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