1 // library.cpp
2 // Created 8/23/2009 by RJ Ryan (rryan@mit.edu)
3 
4 #include "library/library.h"
5 
6 #include <QDir>
7 #include <QItemSelectionModel>
8 #include <QMessageBox>
9 #include <QPointer>
10 #include <QTranslator>
11 
12 #include "controllers/keyboard/keyboardeventfilter.h"
13 #include "database/mixxxdb.h"
14 #include "library/analysisfeature.h"
15 #include "library/autodj/autodjfeature.h"
16 #include "library/banshee/bansheefeature.h"
17 #include "library/browse/browsefeature.h"
18 #include "library/crate/cratefeature.h"
19 #include "library/externaltrackcollection.h"
20 #include "library/itunes/itunesfeature.h"
21 #include "library/library_preferences.h"
22 #include "library/librarycontrol.h"
23 #include "library/libraryfeature.h"
24 #include "library/librarytablemodel.h"
25 #include "library/mixxxlibraryfeature.h"
26 #include "library/playlistfeature.h"
27 #include "library/recording/recordingfeature.h"
28 #include "library/rekordbox/rekordboxfeature.h"
29 #include "library/rhythmbox/rhythmboxfeature.h"
30 #include "library/serato/seratofeature.h"
31 #include "library/setlogfeature.h"
32 #include "library/sidebarmodel.h"
33 #include "library/trackcollection.h"
34 #include "library/trackcollectionmanager.h"
35 #include "library/trackmodel.h"
36 #include "library/traktor/traktorfeature.h"
37 #include "mixer/playermanager.h"
38 #include "moc_library.cpp"
39 #include "recording/recordingmanager.h"
40 #include "util/assert.h"
41 #include "util/db/dbconnectionpooled.h"
42 #include "util/logger.h"
43 #include "util/sandbox.h"
44 #include "widget/wlibrary.h"
45 #include "widget/wlibrarysidebar.h"
46 #include "widget/wsearchlineedit.h"
47 #include "widget/wtracktableview.h"
48 
49 namespace {
50 
51 const mixxx::Logger kLogger("Library");
52 
53 } // anonymous namespace
54 
55 //static
56 const QString Library::kConfigGroup("[Library]");
57 
58 // This is the name which we use to register the WTrackTableView with the
59 // WLibrary
60 const QString Library::m_sTrackViewName = QString("WTrackTableView");
61 
62 // The default row height of the library.
63 const int Library::kDefaultRowHeightPx = 20;
64 
Library(QObject * parent,UserSettingsPointer pConfig,mixxx::DbConnectionPoolPtr pDbConnectionPool,TrackCollectionManager * pTrackCollectionManager,PlayerManager * pPlayerManager,RecordingManager * pRecordingManager)65 Library::Library(
66         QObject* parent,
67         UserSettingsPointer pConfig,
68         mixxx::DbConnectionPoolPtr pDbConnectionPool,
69         TrackCollectionManager* pTrackCollectionManager,
70         PlayerManager* pPlayerManager,
71         RecordingManager* pRecordingManager)
72     : QObject(parent),
73       m_pConfig(pConfig),
74       m_pDbConnectionPool(std::move(pDbConnectionPool)),
75       m_pTrackCollectionManager(pTrackCollectionManager),
76       m_pSidebarModel(make_parented<SidebarModel>(this)),
77       m_pLibraryControl(make_parented<LibraryControl>(this)),
78       m_pMixxxLibraryFeature(nullptr),
79       m_pPlaylistFeature(nullptr),
80       m_pCrateFeature(nullptr),
81       m_pAnalysisFeature(nullptr) {
82 
83     qRegisterMetaType<Library::RemovalType>("Library::RemovalType");
84 
85     m_pKeyNotation.reset(new ControlObject(ConfigKey(kConfigGroup, "key_notation")));
86 
87     connect(m_pTrackCollectionManager,
88             &TrackCollectionManager::libraryScanFinished,
89             this,
90             &Library::slotRefreshLibraryModels);
91 
92     // TODO(rryan) -- turn this construction / adding of features into a static
93     // method or something -- CreateDefaultLibrary
94     m_pMixxxLibraryFeature = new MixxxLibraryFeature(
95             this,
96             m_pConfig);
97     addFeature(m_pMixxxLibraryFeature);
98 
99     addFeature(new AutoDJFeature(this, m_pConfig, pPlayerManager));
100     m_pPlaylistFeature = new PlaylistFeature(this, UserSettingsPointer(m_pConfig));
101     addFeature(m_pPlaylistFeature);
102     m_pCrateFeature = new CrateFeature(this, m_pConfig);
103     addFeature(m_pCrateFeature);
104 
105     BrowseFeature* browseFeature = new BrowseFeature(
106         this, m_pConfig, pRecordingManager);
107     connect(browseFeature,
108             &BrowseFeature::scanLibrary,
109             m_pTrackCollectionManager,
110             &TrackCollectionManager::startLibraryScan);
111     connect(m_pTrackCollectionManager,
112             &TrackCollectionManager::libraryScanStarted,
113             browseFeature,
114             &BrowseFeature::slotLibraryScanStarted);
115     connect(m_pTrackCollectionManager,
116             &TrackCollectionManager::libraryScanFinished,
117             browseFeature,
118             &BrowseFeature::slotLibraryScanFinished);
119     addFeature(browseFeature);
120 
121     addFeature(new RecordingFeature(this, m_pConfig, pRecordingManager));
122     addFeature(new SetlogFeature(this, UserSettingsPointer(m_pConfig)));
123 
124     m_pAnalysisFeature = new AnalysisFeature(this, m_pConfig);
125     connect(m_pPlaylistFeature, &PlaylistFeature::analyzeTracks,
126             m_pAnalysisFeature, &AnalysisFeature::analyzeTracks);
127     connect(m_pCrateFeature, &CrateFeature::analyzeTracks,
128             m_pAnalysisFeature, &AnalysisFeature::analyzeTracks);
129     addFeature(m_pAnalysisFeature);
130     // Suspend a batch analysis while an ad-hoc analysis of
131     // loaded tracks is in progress and resume it afterwards.
132     connect(pPlayerManager, &PlayerManager::trackAnalyzerProgress,
133             this, &Library::onPlayerManagerTrackAnalyzerProgress);
134     connect(pPlayerManager, &PlayerManager::trackAnalyzerIdle,
135             this, &Library::onPlayerManagerTrackAnalyzerIdle);
136 
137     //iTunes and Rhythmbox should be last until we no longer have an obnoxious
138     //messagebox popup when you select them. (This forces you to reach for your
139     //mouse or keyboard if you're using MIDI control and you scroll through them...)
140     if (RhythmboxFeature::isSupported() &&
141         m_pConfig->getValue(ConfigKey(kConfigGroup,"ShowRhythmboxLibrary"), true)) {
142         addFeature(new RhythmboxFeature(this, m_pConfig));
143     }
144     if (m_pConfig->getValue(ConfigKey(kConfigGroup,"ShowBansheeLibrary"), true)) {
145         BansheeFeature::prepareDbPath(m_pConfig);
146         if (BansheeFeature::isSupported()) {
147             addFeature(new BansheeFeature(this, m_pConfig));
148         }
149     }
150     if (ITunesFeature::isSupported() &&
151         m_pConfig->getValue(ConfigKey(kConfigGroup,"ShowITunesLibrary"), true)) {
152         addFeature(new ITunesFeature(this, m_pConfig));
153     }
154     if (TraktorFeature::isSupported() &&
155         m_pConfig->getValue(ConfigKey(kConfigGroup,"ShowTraktorLibrary"), true)) {
156         addFeature(new TraktorFeature(this, m_pConfig));
157     }
158 
159     // TODO(XXX) Rekordbox feature added persistently as the only way to enable it to
160     // dynamically appear/disappear when correctly prepared removable devices
161     // are mounted/unmounted would be to have some form of timed thread to check
162     // periodically. Not ideal performance wise.
163     if (m_pConfig->getValue(ConfigKey(kConfigGroup, "ShowRekordboxLibrary"), true)) {
164         addFeature(new RekordboxFeature(this, m_pConfig));
165     }
166 
167     if (m_pConfig->getValue(ConfigKey(kConfigGroup, "ShowSeratoLibrary"), true)) {
168         addFeature(new SeratoFeature(this, m_pConfig));
169     }
170 
171     for (const auto& externalTrackCollection : m_pTrackCollectionManager->externalCollections()) {
172         auto* feature = externalTrackCollection->newLibraryFeature(this, m_pConfig);
173         if (feature) {
174             kLogger.info()
175                     << "Adding library feature for"
176                     << externalTrackCollection->name();
177             addFeature(feature);
178         } else {
179             kLogger.info()
180                     << "Library feature for"
181                     << externalTrackCollection->name()
182                     << "is not available";
183         }
184     }
185 
186     // On startup we need to check if all of the user's library folders are
187     // accessible to us. If the user is using a database from <1.12.0 with
188     // sandboxing then we will need them to give us permission.
189     qDebug() << "Checking for access to user's library directories:";
190     foreach (QString directoryPath, getDirs()) {
191         bool hasAccess = Sandbox::askForAccess(directoryPath);
192         qDebug() << "Checking for access to" << directoryPath << ":" << hasAccess;
193     }
194 
195     m_iTrackTableRowHeight = m_pConfig->getValue(
196             ConfigKey(kConfigGroup, "RowHeight"), kDefaultRowHeightPx);
197     QString fontStr = m_pConfig->getValueString(ConfigKey(kConfigGroup, "Font"));
198     if (!fontStr.isEmpty()) {
199         m_trackTableFont.fromString(fontStr);
200     } else {
201         m_trackTableFont = QApplication::font();
202     }
203 
204     m_editMetadataSelectedClick = m_pConfig->getValue(
205             ConfigKey(kConfigGroup, "EditMetadataSelectedClick"),
206             PREF_LIBRARY_EDIT_METADATA_DEFAULT);
207 }
208 
~Library()209 Library::~Library() {
210     // Empty but required due to forward declarations in header file!
211 }
212 
trackCollections() const213 TrackCollectionManager* Library::trackCollections() const {
214     // Cannot be implemented inline due to forward declarations
215     return m_pTrackCollectionManager;
216 }
217 
stopPendingTasks()218 void Library::stopPendingTasks() {
219     if (m_pAnalysisFeature) {
220         m_pAnalysisFeature->stopAnalysis();
221         m_pAnalysisFeature = nullptr;
222     }
223 }
224 
bindSearchboxWidget(WSearchLineEdit * pSearchboxWidget)225 void Library::bindSearchboxWidget(WSearchLineEdit* pSearchboxWidget) {
226     connect(pSearchboxWidget,
227             &WSearchLineEdit::search,
228             this,
229             &Library::search);
230     connect(this,
231             &Library::disableSearch,
232             pSearchboxWidget,
233             &WSearchLineEdit::slotDisableSearch);
234     connect(this,
235             &Library::restoreSearch,
236             pSearchboxWidget,
237             &WSearchLineEdit::slotRestoreSearch);
238     connect(this,
239             &Library::setTrackTableFont,
240             pSearchboxWidget,
241             &WSearchLineEdit::slotSetFont);
242     emit setTrackTableFont(m_trackTableFont);
243     m_pLibraryControl->bindSearchboxWidget(pSearchboxWidget);
244 }
245 
bindSidebarWidget(WLibrarySidebar * pSidebarWidget)246 void Library::bindSidebarWidget(WLibrarySidebar* pSidebarWidget) {
247     m_pLibraryControl->bindSidebarWidget(pSidebarWidget);
248 
249     // Setup the sources view
250     pSidebarWidget->setModel(m_pSidebarModel);
251     connect(m_pSidebarModel,
252             &SidebarModel::selectIndex,
253             pSidebarWidget,
254             &WLibrarySidebar::selectIndex);
255     connect(pSidebarWidget,
256             &WLibrarySidebar::pressed,
257             m_pSidebarModel,
258             &SidebarModel::pressed);
259     connect(pSidebarWidget,
260             &WLibrarySidebar::clicked,
261             m_pSidebarModel,
262             &SidebarModel::clicked);
263     // Lazy model: Let triangle symbol increment the model
264     connect(pSidebarWidget,
265             &WLibrarySidebar::expanded,
266             m_pSidebarModel,
267             &SidebarModel::doubleClicked);
268 
269     connect(pSidebarWidget,
270             &WLibrarySidebar::rightClicked,
271             m_pSidebarModel,
272             &SidebarModel::rightClicked);
273 
274     pSidebarWidget->slotSetFont(m_trackTableFont);
275     connect(this,
276             &Library::setTrackTableFont,
277             pSidebarWidget,
278             &WLibrarySidebar::slotSetFont);
279 
280     for (const auto& feature : qAsConst(m_features)) {
281         feature->bindSidebarWidget(pSidebarWidget);
282     }
283 }
284 
bindLibraryWidget(WLibrary * pLibraryWidget,KeyboardEventFilter * pKeyboard)285 void Library::bindLibraryWidget(WLibrary* pLibraryWidget,
286                          KeyboardEventFilter* pKeyboard) {
287     WTrackTableView* pTrackTableView =
288             new WTrackTableView(
289                     pLibraryWidget,
290                     m_pConfig,
291                     m_pTrackCollectionManager,
292                     pLibraryWidget->getTrackTableBackgroundColorOpacity(),
293                     true);
294     pTrackTableView->installEventFilter(pKeyboard);
295     connect(this,
296             &Library::showTrackModel,
297             pTrackTableView,
298             &WTrackTableView::loadTrackModel);
299     connect(pTrackTableView,
300             &WTrackTableView::loadTrack,
301             this,
302             &Library::slotLoadTrack);
303     connect(pTrackTableView,
304             &WTrackTableView::loadTrackToPlayer,
305             this,
306             &Library::slotLoadTrackToPlayer);
307     pLibraryWidget->registerView(m_sTrackViewName, pTrackTableView);
308 
309     connect(this,
310             &Library::switchToView,
311             pLibraryWidget,
312             &WLibrary::switchToView);
313 
314     connect(pTrackTableView,
315             &WTrackTableView::trackSelected,
316             this,
317             &Library::trackSelected);
318 
319     connect(this,
320             &Library::setTrackTableFont,
321             pTrackTableView,
322             &WTrackTableView::setTrackTableFont);
323     connect(this,
324             &Library::setTrackTableRowHeight,
325             pTrackTableView,
326             &WTrackTableView::setTrackTableRowHeight);
327     connect(this,
328             &Library::setSelectedClick,
329             pTrackTableView,
330             &WTrackTableView::setSelectedClick);
331 
332     m_pLibraryControl->bindLibraryWidget(pLibraryWidget, pKeyboard);
333 
334     for (const auto& feature : qAsConst(m_features)) {
335         feature->bindLibraryWidget(pLibraryWidget, pKeyboard);
336     }
337 
338     // Set the current font and row height on all the WTrackTableViews that were
339     // just connected to us.
340     emit setTrackTableFont(m_trackTableFont);
341     emit setTrackTableRowHeight(m_iTrackTableRowHeight);
342     emit setSelectedClick(m_editMetadataSelectedClick);
343 }
344 
addFeature(LibraryFeature * feature)345 void Library::addFeature(LibraryFeature* feature) {
346     VERIFY_OR_DEBUG_ASSERT(feature) {
347         return;
348     }
349     m_features.push_back(feature);
350     m_pSidebarModel->addLibraryFeature(feature);
351     connect(feature,
352             &LibraryFeature::showTrackModel,
353             this,
354             &Library::slotShowTrackModel);
355     connect(feature,
356             &LibraryFeature::switchToView,
357             this,
358             &Library::slotSwitchToView);
359     connect(feature,
360             &LibraryFeature::loadTrack,
361             this,
362             &Library::slotLoadTrack);
363     connect(feature,
364             &LibraryFeature::loadTrackToPlayer,
365             this,
366             &Library::slotLoadTrackToPlayer);
367     connect(feature,
368             &LibraryFeature::restoreSearch,
369             this,
370             &Library::restoreSearch); // forward signal
371     connect(feature,
372             &LibraryFeature::disableSearch,
373             this,
374             &Library::disableSearch); // forward signal
375     connect(feature,
376             &LibraryFeature::enableCoverArtDisplay,
377             this,
378             &Library::enableCoverArtDisplay);
379     connect(feature,
380             &LibraryFeature::trackSelected,
381             this,
382             &Library::trackSelected);
383 }
384 
onPlayerManagerTrackAnalyzerProgress(TrackId,AnalyzerProgress)385 void Library::onPlayerManagerTrackAnalyzerProgress(
386         TrackId /*trackId*/,AnalyzerProgress /*analyzerProgress*/) {
387     if (m_pAnalysisFeature) {
388         m_pAnalysisFeature->suspendAnalysis();
389     }
390 }
391 
onPlayerManagerTrackAnalyzerIdle()392 void Library::onPlayerManagerTrackAnalyzerIdle() {
393     if (m_pAnalysisFeature) {
394         m_pAnalysisFeature->resumeAnalysis();
395     }
396 }
397 
slotShowTrackModel(QAbstractItemModel * model)398 void Library::slotShowTrackModel(QAbstractItemModel* model) {
399     //qDebug() << "Library::slotShowTrackModel" << model;
400     TrackModel* trackModel = dynamic_cast<TrackModel*>(model);
401     VERIFY_OR_DEBUG_ASSERT(trackModel) {
402         return;
403     }
404     emit showTrackModel(model);
405     emit switchToView(m_sTrackViewName);
406     emit restoreSearch(trackModel->currentSearch());
407 }
408 
slotSwitchToView(const QString & view)409 void Library::slotSwitchToView(const QString& view) {
410     //qDebug() << "Library::slotSwitchToView" << view;
411     emit switchToView(view);
412 }
413 
slotLoadTrack(TrackPointer pTrack)414 void Library::slotLoadTrack(TrackPointer pTrack) {
415     emit loadTrack(pTrack);
416 }
417 
slotLoadLocationToPlayer(const QString & location,const QString & group)418 void Library::slotLoadLocationToPlayer(const QString& location, const QString& group) {
419     auto trackRef = TrackRef::fromFileInfo(location);
420     TrackPointer pTrack = m_pTrackCollectionManager->getOrAddTrack(trackRef);
421     if (pTrack) {
422         emit loadTrackToPlayer(pTrack, group);
423     }
424 }
425 
slotLoadTrackToPlayer(TrackPointer pTrack,const QString & group,bool play)426 void Library::slotLoadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play) {
427     emit loadTrackToPlayer(pTrack, group, play);
428 }
429 
slotRefreshLibraryModels()430 void Library::slotRefreshLibraryModels() {
431    m_pMixxxLibraryFeature->refreshLibraryModels();
432    m_pAnalysisFeature->refreshLibraryModels();
433 }
434 
slotCreatePlaylist()435 void Library::slotCreatePlaylist() {
436     m_pPlaylistFeature->slotCreatePlaylist();
437 }
438 
slotCreateCrate()439 void Library::slotCreateCrate() {
440     m_pCrateFeature->slotCreateCrate();
441 }
442 
onSkinLoadFinished()443 void Library::onSkinLoadFinished() {
444     // Enable the default selection when a new skin is loaded.
445     m_pSidebarModel->activateDefaultSelection();
446 }
447 
slotRequestAddDir(const QString & dir)448 void Library::slotRequestAddDir(const QString& dir) {
449     // We only call this method if the user has picked a new directory via a
450     // file dialog. This means the system sandboxer (if we are sandboxed) has
451     // granted us permission to this folder. Create a security bookmark while we
452     // have permission so that we can access the folder on future runs. We need
453     // to canonicalize the path so we first wrap the directory string with a
454     // QDir.
455     QDir directory(dir);
456     Sandbox::createSecurityToken(directory);
457 
458     if (!m_pTrackCollectionManager->addDirectory(dir)) {
459         QMessageBox::information(nullptr,
460                 tr("Add Directory to Library"),
461                 tr("Could not add the directory to your library. Either this "
462                    "directory is already in your library or you are currently "
463                    "rescanning your library."));
464     }
465     // set at least one directory in the config file so that it will be possible
466     // to downgrade from 1.12
467     if (m_pConfig->getValueString(PREF_LEGACY_LIBRARY_DIR).length() < 1) {
468         m_pConfig->set(PREF_LEGACY_LIBRARY_DIR, dir);
469     }
470 }
471 
slotRequestRemoveDir(const QString & dir,RemovalType removalType)472 void Library::slotRequestRemoveDir(const QString& dir, RemovalType removalType) {
473     switch (removalType) {
474     case RemovalType::KeepTracks:
475         break;
476     case RemovalType::HideTracks:
477         // Mark all tracks in this directory as deleted but DON'T purge them
478         // in case the user re-adds them manually.
479         m_pTrackCollectionManager->hideAllTracks(dir);
480         break;
481     case RemovalType::PurgeTracks:
482         // The user requested that we purge all metadata.
483         m_pTrackCollectionManager->purgeAllTracks(dir);
484         break;
485     default:
486         DEBUG_ASSERT(!"unreachable");
487     }
488 
489     // Remove the directory from the directory list.
490     m_pTrackCollectionManager->removeDirectory(dir);
491 
492     // Also update the config file if necessary so that downgrading is still
493     // possible.
494     QString confDir = m_pConfig->getValueString(PREF_LEGACY_LIBRARY_DIR);
495 
496     if (QDir(dir) == QDir(confDir)) {
497         QStringList dirList = getDirs();
498         if (!dirList.isEmpty()) {
499             m_pConfig->set(PREF_LEGACY_LIBRARY_DIR, dirList.first());
500         } else {
501             // Save empty string so that an old version of mixxx knows it has to
502             // ask for a new directory.
503             m_pConfig->set(PREF_LEGACY_LIBRARY_DIR, QString());
504         }
505     }
506 }
507 
slotRequestRelocateDir(const QString & oldDir,const QString & newDir)508 void Library::slotRequestRelocateDir(const QString& oldDir, const QString& newDir) {
509     m_pTrackCollectionManager->relocateDirectory(oldDir, newDir);
510 
511     // also update the config file if necessary so that downgrading is still
512     // possible
513     QString conDir = m_pConfig->getValueString(PREF_LEGACY_LIBRARY_DIR);
514     if (oldDir == conDir) {
515         m_pConfig->set(PREF_LEGACY_LIBRARY_DIR, newDir);
516     }
517 }
518 
getDirs()519 QStringList Library::getDirs() {
520     return m_pTrackCollectionManager->internalCollection()->getDirectoryDAO().getDirs();
521 }
522 
setFont(const QFont & font)523 void Library::setFont(const QFont& font) {
524     QFontMetrics currMetrics(m_trackTableFont);
525     QFontMetrics newMetrics(font);
526     double currFontHeight = currMetrics.height();
527     double newFontHeight = newMetrics.height();
528 
529     m_trackTableFont = font;
530     emit setTrackTableFont(font);
531 
532     // adapt the previous font height/row height ratio
533     int scaledRowHeight = static_cast<int>(std::round(
534             (newFontHeight / currFontHeight) * m_iTrackTableRowHeight));
535     setRowHeight(scaledRowHeight);
536 }
537 
setRowHeight(int rowHeight)538 void Library::setRowHeight(int rowHeight) {
539     m_iTrackTableRowHeight = rowHeight;
540     emit setTrackTableRowHeight(rowHeight);
541 }
542 
setEditMedatataSelectedClick(bool enabled)543 void Library::setEditMedatataSelectedClick(bool enabled) {
544     m_editMetadataSelectedClick = enabled;
545     emit setSelectedClick(enabled);
546 }
547 
trackCollection()548 TrackCollection& Library::trackCollection() {
549     DEBUG_ASSERT(m_pTrackCollectionManager);
550     DEBUG_ASSERT(m_pTrackCollectionManager->internalCollection());
551     return *m_pTrackCollectionManager->internalCollection();
552 }
553