1 #include "library/dlgtagfetcher.h"
2 
3 #include <QTreeWidget>
4 #include <QtDebug>
5 
6 #include "moc_dlgtagfetcher.cpp"
7 #include "track/track.h"
8 #include "track/tracknumbers.h"
9 
10 namespace {
11 
trackColumnValues(const Track & track)12 QStringList trackColumnValues(
13         const Track& track) {
14     mixxx::TrackMetadata trackMetadata;
15     track.readTrackMetadata(&trackMetadata);
16     const QString trackNumberAndTotal = TrackNumbers::joinAsString(
17             trackMetadata.getTrackInfo().getTrackNumber(),
18             trackMetadata.getTrackInfo().getTrackTotal());
19     QStringList columnValues;
20     columnValues.reserve(6);
21     columnValues
22             << trackMetadata.getTrackInfo().getTitle()
23             << trackMetadata.getTrackInfo().getArtist()
24             << trackMetadata.getAlbumInfo().getTitle()
25             << trackMetadata.getTrackInfo().getYear()
26             << trackNumberAndTotal
27             << trackMetadata.getAlbumInfo().getArtist();
28     return columnValues;
29 }
30 
trackReleaseColumnValues(const mixxx::musicbrainz::TrackRelease & trackRelease)31 QStringList trackReleaseColumnValues(
32         const mixxx::musicbrainz::TrackRelease& trackRelease) {
33     const QString trackNumberAndTotal = TrackNumbers::joinAsString(
34             trackRelease.trackNumber,
35             trackRelease.trackTotal);
36     QStringList columnValues;
37     columnValues.reserve(6);
38     columnValues
39             << trackRelease.title
40             << trackRelease.artist
41             << trackRelease.albumTitle
42             << trackRelease.date
43             << trackNumberAndTotal
44             << trackRelease.albumArtist;
45     return columnValues;
46 }
47 
addTrack(const QStringList & trackRow,int resultIndex,QTreeWidget * parent)48 void addTrack(
49         const QStringList& trackRow,
50         int resultIndex,
51         QTreeWidget* parent) {
52     QTreeWidgetItem* item = new QTreeWidgetItem(parent, trackRow);
53     item->setData(0, Qt::UserRole, resultIndex);
54     item->setData(0, Qt::TextAlignmentRole, Qt::AlignLeft);
55 }
56 
57 } // anonymous namespace
58 
DlgTagFetcher(const TrackModel * pTrackModel)59 DlgTagFetcher::DlgTagFetcher(
60         const TrackModel* pTrackModel)
61         // No parent because otherwise it inherits the style parent's
62         // style which can make it unreadable. Bug #673411
63         : QDialog(nullptr),
64           m_pTrackModel(pTrackModel),
65           m_tagFetcher(this),
66           m_networkResult(NetworkResult::Ok) {
67     init();
68 }
69 
init()70 void DlgTagFetcher::init() {
71     setupUi(this);
72 
73     if (m_pTrackModel) {
74         connect(btnPrev, &QPushButton::clicked, this, &DlgTagFetcher::slotPrev);
75         connect(btnNext, &QPushButton::clicked, this, &DlgTagFetcher::slotNext);
76     } else {
77         btnNext->hide();
78         btnPrev->hide();
79     }
80     connect(btnApply, &QPushButton::clicked, this, &DlgTagFetcher::apply);
81     connect(btnQuit, &QPushButton::clicked, this, &DlgTagFetcher::quit);
82     connect(results, &QTreeWidget::currentItemChanged, this, &DlgTagFetcher::resultSelected);
83 
84     connect(&m_tagFetcher, &TagFetcher::resultAvailable, this, &DlgTagFetcher::fetchTagFinished);
85     connect(&m_tagFetcher, &TagFetcher::fetchProgress, this, &DlgTagFetcher::fetchTagProgress);
86     connect(&m_tagFetcher, &TagFetcher::networkError, this, &DlgTagFetcher::slotNetworkResult);
87 }
88 
slotNext()89 void DlgTagFetcher::slotNext() {
90     QModelIndex nextRow = m_currentTrackIndex.sibling(
91             m_currentTrackIndex.row() + 1, m_currentTrackIndex.column());
92     if (nextRow.isValid()) {
93         loadTrack(nextRow);
94         emit next();
95     }
96 }
97 
slotPrev()98 void DlgTagFetcher::slotPrev() {
99     QModelIndex prevRow = m_currentTrackIndex.sibling(
100             m_currentTrackIndex.row() - 1, m_currentTrackIndex.column());
101     if (prevRow.isValid()) {
102         loadTrack(prevRow);
103         emit previous();
104     }
105 }
106 
loadTrackInternal(const TrackPointer & track)107 void DlgTagFetcher::loadTrackInternal(const TrackPointer& track) {
108     if (!track) {
109         return;
110     }
111     results->clear();
112     disconnect(m_track.get(),
113             &Track::changed,
114             this,
115             &DlgTagFetcher::slotTrackChanged);
116 
117     m_track = track;
118     m_data = Data();
119     m_networkResult = NetworkResult::Ok;
120 
121     connect(m_track.get(),
122             &Track::changed,
123             this,
124             &DlgTagFetcher::slotTrackChanged);
125 
126     m_tagFetcher.startFetch(m_track);
127 
128     updateStack();
129 }
130 
loadTrack(const TrackPointer & track)131 void DlgTagFetcher::loadTrack(const TrackPointer& track) {
132     VERIFY_OR_DEBUG_ASSERT(!m_pTrackModel) {
133         return;
134     }
135     loadTrackInternal(track);
136 }
137 
loadTrack(const QModelIndex & index)138 void DlgTagFetcher::loadTrack(const QModelIndex& index) {
139     VERIFY_OR_DEBUG_ASSERT(m_pTrackModel) {
140         return;
141     }
142     TrackPointer pTrack = m_pTrackModel->getTrack(index);
143     m_currentTrackIndex = index;
144     loadTrackInternal(pTrack);
145 }
146 
slotTrackChanged(TrackId trackId)147 void DlgTagFetcher::slotTrackChanged(TrackId trackId) {
148     if (m_track && m_track->getId() == trackId) {
149         updateStack();
150     }
151 }
152 
apply()153 void DlgTagFetcher::apply() {
154     int resultIndex = m_data.m_selectedResult;
155     if (resultIndex < 0) {
156         return;
157     }
158     DEBUG_ASSERT(resultIndex < m_data.m_results.size());
159     const mixxx::musicbrainz::TrackRelease& trackRelease =
160             m_data.m_results[resultIndex];
161     mixxx::TrackMetadata trackMetadata;
162     m_track->readTrackMetadata(&trackMetadata);
163     if (!trackRelease.artist.isEmpty()) {
164         trackMetadata.refTrackInfo().setArtist(
165                 trackRelease.artist);
166     }
167     if (!trackRelease.title.isEmpty()) {
168         trackMetadata.refTrackInfo().setTitle(
169                 trackRelease.title);
170     }
171     if (!trackRelease.trackNumber.isEmpty()) {
172         trackMetadata.refTrackInfo().setTrackNumber(
173                 trackRelease.trackNumber);
174     }
175     if (!trackRelease.trackTotal.isEmpty()) {
176         trackMetadata.refTrackInfo().setTrackTotal(
177                 trackRelease.trackTotal);
178     }
179     if (!trackRelease.date.isEmpty()) {
180         trackMetadata.refTrackInfo().setYear(
181                 trackRelease.date);
182     }
183     if (!trackRelease.albumArtist.isEmpty()) {
184         trackMetadata.refAlbumInfo().setArtist(
185                 trackRelease.albumArtist);
186     }
187     if (!trackRelease.albumTitle.isEmpty()) {
188         trackMetadata.refAlbumInfo().setTitle(
189                 trackRelease.albumTitle);
190     }
191 #if defined(__EXTRA_METADATA__)
192     if (!trackRelease.artistId.isNull()) {
193         trackMetadata.refTrackInfo().setMusicBrainzArtistId(
194                 trackRelease.artistId);
195     }
196     if (!trackRelease.recordingId.isNull()) {
197         trackMetadata.refTrackInfo().setMusicBrainzRecordingId(
198                 trackRelease.recordingId);
199     }
200     if (!trackRelease.trackReleaseId.isNull()) {
201         trackMetadata.refTrackInfo().setMusicBrainzReleaseId(
202                 trackRelease.trackReleaseId);
203     }
204     if (!trackRelease.albumArtistId.isNull()) {
205         trackMetadata.refAlbumInfo().setMusicBrainzArtistId(
206                 trackRelease.albumArtistId);
207     }
208     if (!trackRelease.albumReleaseId.isNull()) {
209         trackMetadata.refAlbumInfo().setMusicBrainzReleaseId(
210                 trackRelease.albumReleaseId);
211     }
212     if (!trackRelease.releaseGroupId.isNull()) {
213         trackMetadata.refAlbumInfo().setMusicBrainzReleaseGroupId(
214                 trackRelease.releaseGroupId);
215     }
216 #endif // __EXTRA_METADATA__
217     m_track->importMetadata(
218             std::move(trackMetadata),
219             // Prevent re-import of outdated metadata from file tags
220             // by explicitly setting the synchronization time stamp
221             // to the current time.
222             QDateTime::currentDateTimeUtc());
223 }
224 
quit()225 void DlgTagFetcher::quit() {
226     m_tagFetcher.cancel();
227     accept();
228 }
229 
fetchTagProgress(const QString & text)230 void DlgTagFetcher::fetchTagProgress(const QString& text) {
231     QString status = tr("Status: %1");
232     loadingStatus->setText(status.arg(text));
233 }
234 
fetchTagFinished(TrackPointer pTrack,const QList<mixxx::musicbrainz::TrackRelease> & guessedTrackReleases)235 void DlgTagFetcher::fetchTagFinished(
236         TrackPointer pTrack,
237         const QList<mixxx::musicbrainz::TrackRelease>& guessedTrackReleases) {
238     VERIFY_OR_DEBUG_ASSERT(pTrack == m_track) {
239         return;
240     }
241     m_data.m_pending = false;
242     m_data.m_results = guessedTrackReleases;
243     // qDebug() << "number of results = " << guessedTrackReleases.size();
244     updateStack();
245 }
246 
slotNetworkResult(int httpError,const QString & app,const QString & message,int code)247 void DlgTagFetcher::slotNetworkResult(
248         int httpError, const QString& app, const QString& message, int code) {
249     m_networkResult = httpError == 0 ? NetworkResult::UnknownError : NetworkResult::HttpError;
250     m_data.m_pending = false;
251     QString strError = tr("HTTP Status: %1");
252     QString strCode = tr("Code: %1");
253     httpStatus->setText(strError.arg(httpError) + "\n" + strCode.arg(code) + "\n" + message);
254     QString unknownError = tr("Mixxx can't connect to %1 for an unknown reason.");
255     cantConnectMessage->setText(unknownError.arg(app));
256     QString cantConnect = tr("Mixxx can't connect to %1.");
257     cantConnectHttp->setText(cantConnect.arg(app));
258     updateStack();
259 }
260 
updateStack()261 void DlgTagFetcher::updateStack() {
262     if (m_data.m_pending) {
263         stack->setCurrentWidget(loading_page);
264         return;
265     } else if (m_networkResult == NetworkResult::HttpError) {
266         stack->setCurrentWidget(networkError_page);
267         return;
268     } else if (m_networkResult == NetworkResult::UnknownError) {
269         stack->setCurrentWidget(generalnetworkError_page);
270         return;
271     } else if (m_data.m_results.isEmpty()) {
272         stack->setCurrentWidget(error_page);
273         return;
274     }
275     btnApply->setEnabled(true);
276     stack->setCurrentWidget(results_page);
277 
278     results->clear();
279 
280     VERIFY_OR_DEBUG_ASSERT(m_track) {
281         return;
282     }
283 
284     addDivider(tr("Original tags"), results);
285     addTrack(trackColumnValues(*m_track), -1, results);
286 
287     addDivider(tr("Suggested tags"), results);
288     {
289         int trackIndex = 0;
290         QSet<QStringList> allColumnValues; // deduplication
291         for (const auto& trackRelease : qAsConst(m_data.m_results)) {
292             const auto columnValues = trackReleaseColumnValues(trackRelease);
293             // Ignore duplicate results
294             if (!allColumnValues.contains(columnValues)) {
295                 allColumnValues.insert(columnValues);
296                 addTrack(columnValues, trackIndex, results);
297             }
298             ++trackIndex;
299         }
300     }
301 
302     for (int i = 0; i < results->model()->columnCount(); i++) {
303         results->resizeColumnToContents(i);
304         int sectionSize = (results->columnWidth(i) + 10);
305         results->header()->resizeSection(i, sectionSize);
306     }
307 
308     // Find the item that was selected last time
309     for (int i = 0; i < results->model()->rowCount(); ++i) {
310         const QModelIndex index = results->model()->index(i, 0);
311         const QVariant id = index.data(Qt::UserRole);
312         if (!id.isNull() && id.toInt() == m_data.m_selectedResult) {
313             results->setCurrentIndex(index);
314             break;
315         }
316     }
317 }
318 
addDivider(const QString & text,QTreeWidget * parent) const319 void DlgTagFetcher::addDivider(const QString& text, QTreeWidget* parent) const {
320     QTreeWidgetItem* item = new QTreeWidgetItem(parent);
321     item->setFirstColumnSpanned(true);
322     item->setText(0, text);
323     item->setFlags(Qt::NoItemFlags);
324     item->setForeground(0, palette().color(QPalette::Disabled, QPalette::Text));
325 
326     QFont bold_font(font());
327     bold_font.setBold(true);
328     item->setFont(0, bold_font);
329 }
330 
resultSelected()331 void DlgTagFetcher::resultSelected() {
332     if (!results->currentItem()) {
333         return;
334     }
335 
336     const int resultIndex = results->currentItem()->data(0, Qt::UserRole).toInt();
337     m_data.m_selectedResult = resultIndex;
338 }
339