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