1 #include "library/basesqltablemodel.h"
2 
3 #include <QUrl>
4 #include <QtDebug>
5 #include <algorithm>
6 
7 #include "library/dao/trackschema.h"
8 #include "library/queryutil.h"
9 #include "library/starrating.h"
10 #include "library/trackcollection.h"
11 #include "library/trackcollectionmanager.h"
12 #include "mixer/playermanager.h"
13 #include "moc_basesqltablemodel.cpp"
14 #include "track/keyutils.h"
15 #include "track/track.h"
16 #include "track/trackmetadata.h"
17 #include "util/assert.h"
18 #include "util/datetime.h"
19 #include "util/db/dbconnection.h"
20 #include "util/duration.h"
21 #include "util/performancetimer.h"
22 #include "util/platform.h"
23 
24 namespace {
25 
26 const bool sDebug = false;
27 
28 // The logic in the following code relies to a track column = 0
29 // Do not change it without changing the logic
30 // Column 0 is skipped when calculating the the columns of the view table
31 const int kIdColumn = 0;
32 const int kMaxSortColumns = 3;
33 
34 // Constant for getModelSetting(name)
35 const QString COLUMNS_SORTING = QStringLiteral("ColumnsSorting");
36 
37 } // anonymous namespace
38 
BaseSqlTableModel(QObject * parent,TrackCollectionManager * pTrackCollectionManager,const char * settingsNamespace)39 BaseSqlTableModel::BaseSqlTableModel(
40         QObject* parent,
41         TrackCollectionManager* pTrackCollectionManager,
42         const char* settingsNamespace)
43         : BaseTrackTableModel(
44                   settingsNamespace,
45                   pTrackCollectionManager,
46                   parent),
47           m_pTrackCollectionManager(pTrackCollectionManager),
48           m_database(pTrackCollectionManager->internalCollection()->database()),
49           m_bInitialized(false) {
50 }
51 
~BaseSqlTableModel()52 BaseSqlTableModel::~BaseSqlTableModel() {
53 }
54 
initHeaderProperties()55 void BaseSqlTableModel::initHeaderProperties() {
56     BaseTrackTableModel::initHeaderProperties();
57     // Add playlist columns
58     setHeaderProperties(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_POSITION,
59             tr("#"),
60             30);
61     setHeaderProperties(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_DATETIMEADDED,
62             tr("Timestamp"),
63             80);
64 }
65 
initSortColumnMapping()66 void BaseSqlTableModel::initSortColumnMapping() {
67     // Add a bijective mapping between the SortColumnIds and column indices
68     for (int i = 0; i < static_cast<int>(TrackModel::SortColumnId::IdMax); ++i) {
69         m_columnIndexBySortColumnId[i] = -1;
70     }
71     m_columnIndexBySortColumnId[static_cast<int>(
72             TrackModel::SortColumnId::Artist)] =
73             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ARTIST);
74     m_columnIndexBySortColumnId[static_cast<int>(
75             TrackModel::SortColumnId::Title)] =
76             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TITLE);
77     m_columnIndexBySortColumnId[static_cast<int>(
78             TrackModel::SortColumnId::Album)] =
79             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUM);
80     m_columnIndexBySortColumnId[static_cast<int>(
81             TrackModel::SortColumnId::AlbumArtist)] =
82             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUMARTIST);
83     m_columnIndexBySortColumnId[static_cast<int>(
84             TrackModel::SortColumnId::Year)] =
85             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_YEAR);
86     m_columnIndexBySortColumnId[static_cast<int>(
87             TrackModel::SortColumnId::Genre)] =
88             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_GENRE);
89     m_columnIndexBySortColumnId[static_cast<int>(
90             TrackModel::SortColumnId::Composer)] =
91             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COMPOSER);
92     m_columnIndexBySortColumnId[static_cast<int>(
93             TrackModel::SortColumnId::Grouping)] =
94             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_GROUPING);
95     m_columnIndexBySortColumnId[static_cast<int>(
96             TrackModel::SortColumnId::TrackNumber)] =
97             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TRACKNUMBER);
98     m_columnIndexBySortColumnId[static_cast<int>(
99             TrackModel::SortColumnId::FileType)] =
100             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_FILETYPE);
101     m_columnIndexBySortColumnId[static_cast<int>(
102             TrackModel::SortColumnId::NativeLocation)] =
103             fieldIndex(ColumnCache::COLUMN_TRACKLOCATIONSTABLE_LOCATION);
104     m_columnIndexBySortColumnId[static_cast<int>(
105             TrackModel::SortColumnId::Comment)] =
106             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COMMENT);
107     m_columnIndexBySortColumnId[static_cast<int>(
108             TrackModel::SortColumnId::Duration)] =
109             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION);
110     m_columnIndexBySortColumnId[static_cast<int>(
111             TrackModel::SortColumnId::BitRate)] =
112             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE);
113     m_columnIndexBySortColumnId[static_cast<int>(
114             TrackModel::SortColumnId::Bpm)] =
115             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM);
116     m_columnIndexBySortColumnId[static_cast<int>(
117             TrackModel::SortColumnId::ReplayGain)] =
118             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN);
119     m_columnIndexBySortColumnId[static_cast<int>(
120             TrackModel::SortColumnId::DateTimeAdded)] =
121             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DATETIMEADDED);
122     m_columnIndexBySortColumnId[static_cast<int>(
123             TrackModel::SortColumnId::TimesPlayed)] =
124             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED);
125     m_columnIndexBySortColumnId[static_cast<int>(
126             TrackModel::SortColumnId::Rating)] =
127             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING);
128     m_columnIndexBySortColumnId[static_cast<int>(
129             TrackModel::SortColumnId::Key)] =
130             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY);
131     m_columnIndexBySortColumnId[static_cast<int>(
132             TrackModel::SortColumnId::Preview)] =
133             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW);
134     m_columnIndexBySortColumnId[static_cast<int>(
135             TrackModel::SortColumnId::Color)] =
136             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COLOR);
137     m_columnIndexBySortColumnId[static_cast<int>(
138             TrackModel::SortColumnId::CoverArt)] =
139             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART);
140     m_columnIndexBySortColumnId[static_cast<int>(
141             TrackModel::SortColumnId::SampleRate)] =
142             fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_SAMPLERATE);
143 
144     m_sortColumnIdByColumnIndex.clear();
145     for (int i = static_cast<int>(TrackModel::SortColumnId::IdMin);
146             i < static_cast<int>(TrackModel::SortColumnId::IdMax);
147             ++i) {
148         TrackModel::SortColumnId sortColumn = static_cast<TrackModel::SortColumnId>(i);
149         m_sortColumnIdByColumnIndex.insert(
150                 m_columnIndexBySortColumnId[static_cast<int>(sortColumn)],
151                 sortColumn);
152     }
153 }
154 
clearRows()155 void BaseSqlTableModel::clearRows() {
156     DEBUG_ASSERT(m_rowInfo.empty() == m_trackIdToRows.empty());
157     DEBUG_ASSERT(m_rowInfo.size() >= m_trackIdToRows.size());
158     if (!m_rowInfo.isEmpty()) {
159         beginRemoveRows(QModelIndex(), 0, m_rowInfo.size() - 1);
160         m_rowInfo.clear();
161         m_trackIdToRows.clear();
162         endRemoveRows();
163     }
164     DEBUG_ASSERT(m_rowInfo.isEmpty());
165     DEBUG_ASSERT(m_trackIdToRows.isEmpty());
166 }
167 
replaceRows(QVector<RowInfo> && rows,TrackId2Rows && trackIdToRows)168 void BaseSqlTableModel::replaceRows(
169         QVector<RowInfo>&& rows,
170         TrackId2Rows&& trackIdToRows) {
171     // NOTE(uklotzde): Use r-value references for parameters here, because
172     // conceptually those parameters should replace the corresponding internal
173     // member variables. Currently Qt4/5 doesn't support move semantics and
174     // instead prevents unnecessary deep copying by implicit sharing (COW)
175     // behind the scenes. Moving would be more efficient, although implicit
176     // sharing meets all requirements. If Qt will ever add move support for
177     // its container types in the future this code becomes even more efficient.
178     DEBUG_ASSERT(rows.empty() == trackIdToRows.empty());
179     DEBUG_ASSERT(rows.size() >= trackIdToRows.size());
180     if (rows.isEmpty()) {
181         clearRows();
182     } else {
183         beginInsertRows(QModelIndex(), 0, rows.size() - 1);
184         m_rowInfo = rows;
185         m_trackIdToRows = trackIdToRows;
186         endInsertRows();
187     }
188 }
189 
select()190 void BaseSqlTableModel::select() {
191     if (!m_bInitialized) {
192         return;
193     }
194     // We should be able to detect when a select() would be a no-op. The DAO's
195     // do not currently broadcast signals for when common things happen. In the
196     // future, we can turn this check on and avoid a lot of needless
197     // select()'s. rryan 9/2011
198     // if (!m_bDirty) {
199     //     if (sDebug) {
200     //         qDebug() << this << "Skipping non-dirty select()";
201     //     }
202     //     return;
203     // }
204 
205     if (sDebug) {
206         qDebug() << this << "select()";
207     }
208 
209     PerformanceTimer time;
210     time.start();
211 
212     // Prepare query for id and all columns not in m_trackSource
213     QString queryString = QString("SELECT %1 FROM %2 %3")
214                                   .arg(m_tableColumns.join(","), m_tableName, m_tableOrderBy);
215 
216     if (sDebug) {
217         qDebug() << this << "select() executing:" << queryString;
218     }
219 
220     QSqlQuery query(m_database);
221     // This causes a memory savings since QSqlCachedResult (what QtSQLite uses)
222     // won't allocate a giant in-memory table that we won't use at all.
223     query.setForwardOnly(true);
224     if (!query.prepare(queryString)) {
225         LOG_FAILED_QUERY(query);
226         return;
227     }
228     if (!query.exec()) {
229         LOG_FAILED_QUERY(query);
230         return;
231     }
232 
233     // Remove all the rows from the table after(!) the query has been
234     // executed successfully. See Bug #1090888.
235     // TODO(rryan) we could edit the table in place instead of clearing it?
236     clearRows();
237 
238     // The size of the result set is not known in advance for a
239     // forward-only query, so we cannot reserve memory for rows
240     // in advance.
241     QVector<RowInfo> rowInfos;
242     QSet<TrackId> trackIds;
243     int idColumn = -1;
244     while (query.next()) {
245         QSqlRecord sqlRecord = query.record();
246 
247         if (idColumn < 0) {
248             idColumn = sqlRecord.indexOf(m_idColumn);
249         }
250         VERIFY_OR_DEBUG_ASSERT(idColumn >= 0) {
251             qCritical()
252                     << "ID column not available in database query results:"
253                     << m_idColumn;
254             return;
255         }
256         // TODO(XXX): Can we get rid of the hard-coded assumption that
257         // the the first column always contains the id?
258         DEBUG_ASSERT(idColumn == kIdColumn);
259 
260         TrackId trackId(sqlRecord.value(idColumn));
261         trackIds.insert(trackId);
262 
263         RowInfo rowInfo;
264         rowInfo.trackId = trackId;
265         // current position defines the ordering
266         rowInfo.order = rowInfos.size();
267         rowInfo.metadata.reserve(sqlRecord.count());
268         for (int i = 0; i < m_tableColumns.size(); ++i) {
269             rowInfo.metadata.push_back(sqlRecord.value(i));
270         }
271         rowInfos.push_back(rowInfo);
272     }
273 
274     if (sDebug) {
275         qDebug() << "Rows actually received:" << rowInfos.size();
276     }
277 
278     if (m_trackSource) {
279         m_trackSource->filterAndSort(trackIds,
280                 m_currentSearch,
281                 m_currentSearchFilter,
282                 m_trackSourceOrderBy,
283                 m_sortColumns,
284                 m_tableColumns.size() - 1, // exclude the 1st column with the id
285                 &m_trackSortOrder);
286 
287         // Re-sort the track IDs since filterAndSort can change their order or mark
288         // them for removal (by setting their row to -1).
289         for (auto& rowInfo : rowInfos) {
290             // If the sort is not a track column then we will sort only to
291             // separate removed tracks (order == -1) from present tracks (order ==
292             // 0). Otherwise we sort by the order that filterAndSort returned to us.
293             if (m_trackSourceOrderBy.isEmpty()) {
294                 rowInfo.order = m_trackSortOrder.contains(rowInfo.trackId) ? 0 : -1;
295             } else {
296                 rowInfo.order = m_trackSortOrder.value(rowInfo.trackId, -1);
297             }
298         }
299     }
300 
301     // RowInfo::operator< sorts by the order field, except -1 is placed at the
302     // end so we can easily slice off rows that are no longer present. Stable
303     // sort is necessary because the tracks may be in pre-sorted order so we
304     // should not disturb that if we are only removing tracks.
305     std::stable_sort(rowInfos.begin(), rowInfos.end());
306 
307     TrackId2Rows trackIdToRows;
308     // We expect almost all rows to be valid and that only a few tracks
309     // are contained multiple times in rowInfos (e.g. in history playlists)
310     trackIdToRows.reserve(rowInfos.size());
311     for (int i = 0; i < rowInfos.size(); ++i) {
312         const RowInfo& rowInfo = rowInfos[i];
313 
314         if (rowInfo.order == -1) {
315             // We've reached the end of valid rows. Resize rowInfo to cut off
316             // this and all further elements.
317             rowInfos.resize(i);
318             break;
319         }
320         trackIdToRows[rowInfo.trackId].push_back(i);
321     }
322     // The number of unique tracks cannot be greater than the
323     // number of total rows returned by the query
324     DEBUG_ASSERT(trackIdToRows.size() <= rowInfos.size());
325 
326     // We're done! Issue the update signals and replace the master maps.
327     replaceRows(
328             std::move(rowInfos),
329             std::move(trackIdToRows));
330     // Both rowInfo and trackIdToRows (might) have been moved and
331     // must not be used afterwards!
332 
333     qDebug() << this << "select() took" << time.elapsed().debugMillisWithUnit()
334              << m_rowInfo.size();
335 }
336 
setTable(const QString & tableName,const QString & idColumn,const QStringList & tableColumns,QSharedPointer<BaseTrackCache> trackSource)337 void BaseSqlTableModel::setTable(const QString& tableName,
338         const QString& idColumn,
339         const QStringList& tableColumns,
340         QSharedPointer<BaseTrackCache> trackSource) {
341     if (sDebug) {
342         qDebug() << this << "setTable" << tableName << tableColumns << idColumn;
343     }
344     m_tableName = tableName;
345     m_idColumn = idColumn;
346     m_tableColumns = tableColumns;
347 
348     if (m_trackSource) {
349         disconnect(m_trackSource.data(),
350                 &BaseTrackCache::tracksChanged,
351                 this,
352                 &BaseSqlTableModel::tracksChanged);
353     }
354     m_trackSource = trackSource;
355     if (m_trackSource) {
356         // It's important that this not be a direct connection, or else the UI
357         // might try to update while a cache operation is in progress, and that
358         // will hit the cache again and cause dangerous reentry cycles
359         // See https://bugs.launchpad.net/mixxx/+bug/1365708
360         // TODO: A better fix is to have cache and trackpointers defer saving
361         // and deleting, so those operations only take place at the top of
362         // the call stack.
363         connect(m_trackSource.data(),
364                 &BaseTrackCache::tracksChanged,
365                 this,
366                 &BaseSqlTableModel::tracksChanged,
367                 Qt::QueuedConnection);
368     }
369 
370     initTableColumnsAndHeaderProperties(m_tableColumns);
371     initSortColumnMapping();
372 
373     m_bInitialized = true;
374 }
375 
columnIndexFromSortColumnId(TrackModel::SortColumnId column) const376 int BaseSqlTableModel::columnIndexFromSortColumnId(TrackModel::SortColumnId column) const {
377     if (column == TrackModel::SortColumnId::Invalid) {
378         return -1;
379     }
380 
381     return m_columnIndexBySortColumnId[static_cast<int>(column)];
382 }
383 
sortColumnIdFromColumnIndex(int index) const384 TrackModel::SortColumnId BaseSqlTableModel::sortColumnIdFromColumnIndex(int index) const {
385     return m_sortColumnIdByColumnIndex.value(index, TrackModel::SortColumnId::Invalid);
386 }
387 
currentSearch() const388 const QString BaseSqlTableModel::currentSearch() const {
389     return m_currentSearch;
390 }
391 
setSearch(const QString & searchText,const QString & extraFilter)392 void BaseSqlTableModel::setSearch(const QString& searchText, const QString& extraFilter) {
393     if (sDebug) {
394         qDebug() << this << "setSearch" << searchText;
395     }
396 
397     bool searchIsDifferent = m_currentSearch.isNull() || m_currentSearch != searchText;
398     bool filterDisabled = (m_currentSearchFilter.isNull() && extraFilter.isNull());
399     bool searchFilterIsDifferent = m_currentSearchFilter != extraFilter;
400 
401     if (!searchIsDifferent && (filterDisabled || !searchFilterIsDifferent)) {
402         // Do nothing if the filters are no different.
403         return;
404     }
405 
406     m_currentSearch = searchText;
407     m_currentSearchFilter = extraFilter;
408 }
409 
search(const QString & searchText,const QString & extraFilter)410 void BaseSqlTableModel::search(const QString& searchText, const QString& extraFilter) {
411     if (sDebug) {
412         qDebug() << this << "search" << searchText;
413     }
414     setSearch(searchText, extraFilter);
415     select();
416 }
417 
setSort(int column,Qt::SortOrder order)418 void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) {
419     if (sDebug) {
420         qDebug() << this << "setSort()" << column << order << m_tableColumns;
421     }
422 
423     int trackSourceColumnCount = m_trackSource ? m_trackSource->columnCount() : 0;
424 
425     if (column < 0 ||
426             column >= trackSourceColumnCount + m_sortColumns.size() - 1) {
427         // -1 because id column is in both tables
428         qWarning() << "BaseSqlTableModel::setSort invalid column:" << column;
429         return;
430     }
431 
432     // There's no item to sort already, load from Settings last sort
433     if (m_sortColumns.isEmpty()) {
434         QString val = getModelSetting(COLUMNS_SORTING);
435         QTextStream in(&val);
436 
437         while (!in.atEnd()) {
438             int ordI = -1;
439             QString name;
440 
441             in >> name >> ordI;
442 
443             int col = fieldIndex(name);
444             if (col < 0) {
445                 continue;
446             }
447 
448             Qt::SortOrder ord;
449             ord = ordI > 0 ? Qt::AscendingOrder : Qt::DescendingOrder;
450 
451             m_sortColumns << SortColumn(col, ord);
452         }
453     }
454     if (m_sortColumns.size() > 0 && m_sortColumns.at(0).m_column == column) {
455         // Only the order has changed
456         m_sortColumns.replace(0, SortColumn(column, order));
457     } else {
458         // Remove column if already in history
459         // As reverse loop to not skip an entry when removing the previous
460         for (int i = m_sortColumns.size() - 1; i >= 0; --i) {
461             if (m_sortColumns.at(i).m_column == column) {
462                 m_sortColumns.removeAt(i);
463                 break;
464             }
465         }
466 
467         // set new sort as head and shift out old sort
468         m_sortColumns.prepend(SortColumn(column, order));
469 
470         if (m_sortColumns.size() > kMaxSortColumns) {
471             m_sortColumns.removeLast();
472         }
473     }
474 
475     // Write new sortColumns order to user settings
476     QString val;
477     QTextStream out(&val);
478     for (SortColumn& sc : m_sortColumns) {
479         QString name;
480         if (sc.m_column > 0 && sc.m_column < m_tableColumns.size()) {
481             name = m_tableColumns[sc.m_column];
482         } else {
483             // ccColumn between 1..x to skip the id column
484             int ccColumn = sc.m_column - m_tableColumns.size() + 1;
485             name = m_trackSource->columnNameForFieldIndex(ccColumn);
486         }
487 
488         out << name << " ";
489         out << (sc.m_order == Qt::AscendingOrder ? 1 : -1) << " ";
490     }
491     out.flush();
492     setModelSetting(COLUMNS_SORTING, val);
493 
494     if (sDebug) {
495         qDebug() << "setSort() sortColumns:" << val;
496     }
497 
498     // we have two selects for sorting, since keeping the select history
499     // across the two selects is hard, we do this only for the trackSource
500     // this is OK, because the columns of the table are virtual in case of
501     // preview column or individual like playlist track number so that we
502     // do not need the history anyway.
503 
504     // reset the old order by clauses
505     m_trackSourceOrderBy.clear();
506     m_tableOrderBy.clear();
507 
508     if (column > 0 && column < m_tableColumns.size()) {
509         // Table sorting, no history
510         if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW)) {
511             // Random sort easter egg
512             m_tableOrderBy = "ORDER BY RANDOM()";
513         } else {
514             m_tableOrderBy = "ORDER BY ";
515             QString field = m_tableColumns[column];
516             QString sort_field = QString("%1.%2").arg(m_tableName, field);
517             m_tableOrderBy.append(mixxx::DbConnection::collateLexicographically(sort_field));
518             m_tableOrderBy.append((order == Qt::AscendingOrder) ? " ASC" : " DESC");
519         }
520         m_sortColumns.clear();
521         m_sortColumns.prepend(SortColumn(column, order));
522     } else if (m_trackSource) {
523         bool first = true;
524         for (const SortColumn& sc : qAsConst(m_sortColumns)) {
525             QString sort_field;
526             if (sc.m_column < m_tableColumns.size()) {
527                 if (sc.m_column == kIdColumn) {
528                     sort_field = m_trackSource->columnSortForFieldIndex(kIdColumn);
529                 } else if (sc.m_column ==
530                         fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW)) {
531                     sort_field = "RANDOM()";
532                 } else {
533                     // we can't sort by other table columns here since primary sort is a track
534                     // column: skip
535                     continue;
536                 }
537             } else {
538                 // + 1 to skip id column
539                 int ccColumn = sc.m_column - m_tableColumns.size() + 1;
540                 sort_field = m_trackSource->columnSortForFieldIndex(ccColumn);
541             }
542             VERIFY_OR_DEBUG_ASSERT(!sort_field.isEmpty()) {
543                 continue;
544             }
545 
546             m_trackSourceOrderBy.append(first ? "ORDER BY " : ", ");
547             m_trackSourceOrderBy.append(sort_field);
548             m_trackSourceOrderBy.append((sc.m_order == Qt::AscendingOrder) ? " ASC" : " DESC");
549             //qDebug() << m_trackSourceOrderBy;
550             first = false;
551         }
552     }
553 }
554 
sort(int column,Qt::SortOrder order)555 void BaseSqlTableModel::sort(int column, Qt::SortOrder order) {
556     if (sDebug) {
557         qDebug() << this << "sort()" << column << order;
558     }
559     setSort(column, order);
560     select();
561 }
562 
rowCount(const QModelIndex & parent) const563 int BaseSqlTableModel::rowCount(const QModelIndex& parent) const {
564     int count = parent.isValid() ? 0 : m_rowInfo.size();
565     //qDebug() << "rowCount()" << parent << count;
566     return count;
567 }
568 
columnCount(const QModelIndex & parent) const569 int BaseSqlTableModel::columnCount(const QModelIndex& parent) const {
570     VERIFY_OR_DEBUG_ASSERT(!parent.isValid()) {
571         return 0;
572     }
573     // Subtract one from trackSource::columnCount to ignore the id column
574     int count = m_tableColumns.size() +
575             (m_trackSource ? m_trackSource->columnCount() - 1 : 0);
576     return count;
577 }
578 
fieldIndex(ColumnCache::Column column) const579 int BaseSqlTableModel::fieldIndex(ColumnCache::Column column) const {
580     int tableIndex = BaseTrackTableModel::fieldIndex(column);
581     if (tableIndex >= 0) {
582         return tableIndex;
583     }
584     if (m_trackSource) {
585         // We need to account for the case where the field name is not a table
586         // column or a source column.
587         int sourceTableIndex = m_trackSource->fieldIndex(column);
588         if (sourceTableIndex > -1) {
589             // Subtract one from the fieldIndex() result to account for the id column
590             return m_tableColumns.size() + sourceTableIndex - 1;
591         }
592     }
593     return tableIndex;
594 }
595 
fieldIndex(const QString & fieldName) const596 int BaseSqlTableModel::fieldIndex(const QString& fieldName) const {
597     int tableIndex = BaseTrackTableModel::fieldIndex(fieldName);
598     if (tableIndex >= 0) {
599         return tableIndex;
600     }
601     if (m_trackSource) {
602         // We need to account for the case where the field name is not a table
603         // column or a source column.
604         int sourceTableIndex = m_trackSource->fieldIndex(fieldName);
605         if (sourceTableIndex > -1) {
606             // Subtract one from the fieldIndex() result to account for the id column
607             return m_tableColumns.size() + sourceTableIndex - 1;
608         }
609     }
610     return tableIndex;
611 }
612 
rawValue(const QModelIndex & index) const613 QVariant BaseSqlTableModel::rawValue(
614         const QModelIndex& index) const {
615     DEBUG_ASSERT(index.isValid());
616 
617     const int row = index.row();
618     DEBUG_ASSERT(row >= 0);
619     if (row >= m_rowInfo.size()) {
620         return QVariant();
621     }
622 
623     const int column = index.column();
624     DEBUG_ASSERT(column >= 0);
625     // TODO(rryan) check range on column
626 
627     const RowInfo& rowInfo = m_rowInfo[row];
628     const TrackId trackId = rowInfo.trackId;
629 
630     // If the row info has the row-specific column, return that.
631     if (column < m_tableColumns.size()) {
632         // Special case for preview column. Return whether trackId is the
633         // current preview deck track.
634         if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW)) {
635             return previewDeckTrackId() == trackId;
636         }
637 
638         const QVector<QVariant>& columns = rowInfo.metadata;
639         if (sDebug) {
640             qDebug() << "Returning table-column value"
641                     << columns.at(column)
642                     << "for column" << column;
643         }
644         return columns[column];
645     }
646 
647     // Otherwise, return the information from the track record cache for the
648     // given track ID
649     if (!m_trackSource) {
650         return QVariant();
651     }
652     // Subtract table columns from index to get the track source column
653     // number and add 1 to skip over the id column.
654     int trackSourceColumn = column - m_tableColumns.size() + 1;
655     if (!m_trackSource->isCached(trackId)) {
656         // Ideally Mixxx would have notified us of this via a signal, but in
657         // the case that a track is not in the cache, we attempt to load it
658         // on the fly. This will be a steep penalty to pay if there are tons
659         // of these tracks in the table that are not cached.
660         qDebug() << __FILE__ << __LINE__
661                     << "Track" << trackId
662                     << "was not present in cache and had to be manually fetched.";
663         m_trackSource->ensureCached(trackId);
664     }
665     return m_trackSource->data(trackId, trackSourceColumn);
666 }
667 
setTrackValueForColumn(const TrackPointer & pTrack,int column,const QVariant & value,int role)668 bool BaseSqlTableModel::setTrackValueForColumn(
669         const TrackPointer& pTrack,
670         int column,
671         const QVariant& value,
672         int role) {
673     if (role != Qt::EditRole) {
674         return false;
675     }
676     // You can't set something in the table columns because we have no way of
677     // persisting it.
678     if (column < m_tableColumns.size()) {
679         return false;
680     }
681 
682     // TODO(XXX) Qt properties could really help here.
683     DEBUG_ASSERT(pTrack);
684     if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ARTIST) == column) {
685         pTrack->setArtist(value.toString());
686     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TITLE) == column) {
687         pTrack->setTitle(value.toString());
688     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUM) == column) {
689         pTrack->setAlbum(value.toString());
690     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUMARTIST) == column) {
691         pTrack->setAlbumArtist(value.toString());
692     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_YEAR) == column) {
693         pTrack->setYear(value.toString());
694     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_GENRE) == column) {
695         pTrack->setGenre(value.toString());
696     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COMPOSER) == column) {
697         pTrack->setComposer(value.toString());
698     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_GROUPING) == column) {
699         pTrack->setGrouping(value.toString());
700     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TRACKNUMBER) == column) {
701         pTrack->setTrackNumber(value.toString());
702     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COMMENT) == column) {
703         pTrack->setComment(value.toString());
704     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM) == column) {
705         pTrack->trySetBpm(static_cast<double>(value.toDouble()));
706     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED) == column) {
707         // Update both the played flag and the number of times played
708         pTrack->updatePlayCounter(value.toBool());
709     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED) == column) {
710         const int timesPlayed = value.toInt();
711         if (0 < timesPlayed) {
712             // Preserve the played flag and only set the number of times played
713             PlayCounter playCounter(pTrack->getPlayCounter());
714             playCounter.setTimesPlayed(timesPlayed);
715             pTrack->setPlayCounter(playCounter);
716         } else {
717             // Reset both the played flag and the number of times played
718             pTrack->resetPlayCounter();
719         }
720     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING) == column) {
721         StarRating starRating = value.value<StarRating>();
722         pTrack->setRating(starRating.starCount());
723     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY) == column) {
724         pTrack->setKeyText(value.toString(),
725                 mixxx::track::io::key::USER);
726     } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK) == column) {
727         pTrack->setBpmLocked(value.toBool());
728     } else {
729         // We never should get up to this point!
730         VERIFY_OR_DEBUG_ASSERT(false) {
731             qWarning() << "Column"
732                        << columnNameForFieldIndex(column)
733                        << "is not editable!";
734         }
735         return false;
736     }
737     return true;
738 }
739 
getTrack(const QModelIndex & index) const740 TrackPointer BaseSqlTableModel::getTrack(const QModelIndex& index) const {
741     return m_pTrackCollectionManager->internalCollection()->getTrackById(getTrackId(index));
742 }
743 
getTrackId(const QModelIndex & index) const744 TrackId BaseSqlTableModel::getTrackId(const QModelIndex& index) const {
745     if (index.isValid()) {
746         return TrackId(index.sibling(index.row(), fieldIndex(m_idColumn)).data());
747     } else {
748         return TrackId();
749     }
750 }
751 
getTrackLocation(const QModelIndex & index) const752 QString BaseSqlTableModel::getTrackLocation(const QModelIndex& index) const {
753     if (!index.isValid()) {
754         return QString();
755     }
756     QString nativeLocation =
757             index.sibling(index.row(),
758                          fieldIndex(ColumnCache::COLUMN_TRACKLOCATIONSTABLE_LOCATION))
759                     .data()
760                     .toString();
761     return QDir::fromNativeSeparators(nativeLocation);
762 }
763 
getCoverInfo(const QModelIndex & index) const764 CoverInfo BaseSqlTableModel::getCoverInfo(const QModelIndex& index) const {
765     CoverInfo coverInfo;
766     coverInfo.hash =
767             index.sibling(index.row(),
768                          fieldIndex(ColumnCache::
769                                          COLUMN_LIBRARYTABLE_COVERART_HASH))
770                     .data()
771                     .toUInt();
772     coverInfo.type = static_cast<CoverInfo::Type>(
773             index.sibling(index.row(),
774                          fieldIndex(ColumnCache::
775                                          COLUMN_LIBRARYTABLE_COVERART_TYPE))
776                     .data()
777                     .toInt());
778     coverInfo.source = static_cast<CoverInfo::Source>(
779             index.sibling(index.row(),
780                          fieldIndex(ColumnCache::
781                                          COLUMN_LIBRARYTABLE_COVERART_SOURCE))
782                     .data()
783                     .toInt());
784     coverInfo.coverLocation =
785             index.sibling(index.row(),
786                          fieldIndex(ColumnCache::
787                                          COLUMN_LIBRARYTABLE_COVERART_LOCATION))
788                     .data()
789                     .toString();
790     coverInfo.trackLocation = getTrackLocation(index);
791     return coverInfo;
792 }
793 
tracksChanged(const QSet<TrackId> & trackIds)794 void BaseSqlTableModel::tracksChanged(const QSet<TrackId>& trackIds) {
795     if (sDebug) {
796         qDebug() << this << "trackChanged" << trackIds.size();
797     }
798 
799     const int numColumns = columnCount();
800     for (const auto& trackId : trackIds) {
801         const auto rows = getTrackRows(trackId);
802         for (int row : rows) {
803             //qDebug() << "Row in this result set was updated. Signalling update. track:" << trackId << "row:" << row;
804             QModelIndex topLeft = index(row, 0);
805             QModelIndex bottomRight = index(row, numColumns);
806             emit dataChanged(topLeft, bottomRight);
807         }
808     }
809 }
810 
hideTracks(const QModelIndexList & indices)811 void BaseSqlTableModel::hideTracks(const QModelIndexList& indices) {
812     QList<TrackId> trackIds;
813     foreach (QModelIndex index, indices) {
814         TrackId trackId(getTrackId(index));
815         trackIds.append(trackId);
816     }
817 
818     m_pTrackCollectionManager->hideTracks(trackIds);
819 
820     // TODO(rryan) : do not select, instead route event to BTC and notify from
821     // there.
822     select(); //Repopulate the data model.
823 }
824 
getTrackRefs(const QModelIndexList & indices) const825 QList<TrackRef> BaseSqlTableModel::getTrackRefs(
826         const QModelIndexList& indices) const {
827     QList<TrackRef> trackRefs;
828     trackRefs.reserve(indices.size());
829     foreach (QModelIndex index, indices) {
830         trackRefs.append(TrackRef::fromFileInfo(
831                 TrackFile(getTrackLocation(index)),
832                 getTrackId(index)));
833     }
834     return trackRefs;
835 }
836