diff options
author | Uwe Klotz <uklotz@mixxx.org> | 2019-09-29 09:53:02 +0200 |
---|---|---|
committer | Uwe Klotz <uklotz@mixxx.org> | 2019-09-29 09:53:02 +0200 |
commit | 1d882d8b5022005a120e23f20496f8cd9795d45c (patch) | |
tree | 5089fa8478860f26ec9e0a3f91f25269285bc76e /src/library | |
parent | a1fdea2aef545ee0bd9f25027f0d66f936325cf8 (diff) | |
parent | 5998ee24e30361b3d14907d899e191a0aee16a3b (diff) |
Merge branch 'master' of git@github.com:mixxxdj/mixxx.git into new-signals-slots-syntax-library
# Conflicts:
# src/library/library.cpp
# src/library/scanner/libraryscanner.cpp
Diffstat (limited to 'src/library')
-rw-r--r-- | src/library/dao/directorydao.cpp | 30 | ||||
-rw-r--r-- | src/library/dao/directorydao.h | 2 | ||||
-rw-r--r-- | src/library/dao/trackdao.cpp | 93 | ||||
-rw-r--r-- | src/library/dao/trackdao.h | 10 | ||||
-rw-r--r-- | src/library/dlghidden.cpp | 2 | ||||
-rw-r--r-- | src/library/dlgmissing.cpp | 2 | ||||
-rw-r--r-- | src/library/externaltrackcollection.cpp | 16 | ||||
-rw-r--r-- | src/library/externaltrackcollection.h | 103 | ||||
-rw-r--r-- | src/library/hiddentablemodel.cpp | 8 | ||||
-rw-r--r-- | src/library/hiddentablemodel.h | 7 | ||||
-rw-r--r-- | src/library/library.cpp | 285 | ||||
-rw-r--r-- | src/library/library.h | 13 | ||||
-rw-r--r-- | src/library/locationdelegate.cpp | 23 | ||||
-rw-r--r-- | src/library/locationdelegate.h | 23 | ||||
-rw-r--r-- | src/library/missingtablemodel.cpp | 15 | ||||
-rw-r--r-- | src/library/missingtablemodel.h | 7 | ||||
-rw-r--r-- | src/library/scanner/libraryscanner.cpp | 43 | ||||
-rw-r--r-- | src/library/scanner/libraryscanner.h | 2 | ||||
-rw-r--r-- | src/library/stareditor.cpp | 5 | ||||
-rw-r--r-- | src/library/tableitemdelegate.cpp | 36 | ||||
-rw-r--r-- | src/library/tableitemdelegate.h | 23 | ||||
-rw-r--r-- | src/library/trackcollection.cpp | 37 | ||||
-rw-r--r-- | src/library/trackcollection.h | 12 |
23 files changed, 648 insertions, 149 deletions
diff --git a/src/library/dao/directorydao.cpp b/src/library/dao/directorydao.cpp index c490ef959d..15338680c7 100644 --- a/src/library/dao/directorydao.cpp +++ b/src/library/dao/directorydao.cpp @@ -14,7 +14,6 @@ int DirectoryDAO::addDirectory(const QString& newDir) { // Do nothing if the dir to add is a child of a directory that is already in // the db. - ScopedTransaction transaction(m_database); QStringList dirs = getDirs(); QString childDir; QString parentDir; @@ -46,7 +45,6 @@ int DirectoryDAO::addDirectory(const QString& newDir) { LOG_FAILED_QUERY(query) << "Adding new dir (" % newDir % ") failed."; return SQL_ERROR; } - transaction.commit(); return ALL_FINE; } @@ -80,13 +78,12 @@ int DirectoryDAO::removeDirectory(const QString& dir) { } -QSet<TrackId> DirectoryDAO::relocateDirectory(const QString& oldFolder, +QList<TrackRef> DirectoryDAO::relocateDirectory(const QString& oldFolder, const QString& newFolder) { // TODO(rryan): This method could use error reporting. It can fail in // mysterious ways for example if a track in the oldFolder also has a zombie // track location in newFolder then the replace query will fail because the // location column becomes non-unique. - ScopedTransaction transaction(m_database); QSqlQuery query(m_database); query.prepare("UPDATE " % DIRECTORYDAO_TABLE % " SET " % DIRECTORYDAO_DIR % "=:newFolder WHERE " % DIRECTORYDAO_DIR % " = :oldFolder"); @@ -95,7 +92,7 @@ QSet<TrackId> DirectoryDAO::relocateDirectory(const QString& oldFolder, if (!query.exec()) { LOG_FAILED_QUERY(query) << "could not relocate directory" << oldFolder << "to" << newFolder; - return QSet<TrackId>(); + return {}; } // on Windows the absolute path starts with the drive name @@ -113,35 +110,32 @@ QSet<TrackId> DirectoryDAO::relocateDirectory(const QString& oldFolder, .arg(startsWithOldFolder, kSqlLikeMatchAll)); if (!query.exec()) { LOG_FAILED_QUERY(query) << "could not relocate path of tracks"; - return QSet<TrackId>(); + return {}; } - QSet<TrackId> trackIds; - QList<int> loc_ids; - QStringList old_locs; + QList<DbId> loc_ids; + QList<TrackRef> trackRefs; while (query.next()) { - trackIds.insert(TrackId(query.value(0))); - loc_ids.append(query.value(1).toInt()); - old_locs.append(query.value(2).toString()); + loc_ids.append(DbId(query.value(1).toInt())); + trackRefs.append(TrackRef::fromFileInfo(query.value(2).toString(), TrackId(query.value(0)))); } QString replacement = "UPDATE track_locations SET location = :newloc " "WHERE id = :id"; query.prepare(replacement); for (int i = 0; i < loc_ids.size(); ++i) { - QString newloc = old_locs.at(i); + QString newloc = trackRefs.at(i).getLocation(); newloc.replace(0, oldFolder.size(), newFolder); query.bindValue("newloc", newloc); - query.bindValue("id", loc_ids.at(i)); + query.bindValue("id", loc_ids.at(i).toVariant()); if (!query.exec()) { LOG_FAILED_QUERY(query) << "could not relocate path of tracks"; - return QSet<TrackId>(); + return {}; } } - qDebug() << "Relocated tracks:" << trackIds.size(); - transaction.commit(); - return trackIds; + qDebug() << "Relocated tracks:" << trackRefs.size(); + return trackRefs; } QStringList DirectoryDAO::getDirs() { diff --git a/src/library/dao/directorydao.h b/src/library/dao/directorydao.h index 827bd4a703..6892021a96 100644 --- a/src/library/dao/directorydao.h +++ b/src/library/dao/directorydao.h @@ -21,7 +21,7 @@ class DirectoryDAO : public DAO { int addDirectory(const QString& dir); int removeDirectory(const QString& dir); - QSet<TrackId> relocateDirectory(const QString& oldFolder, const QString& newFolder); + QList<TrackRef> relocateDirectory(const QString& oldFolder, const QString& newFolder); QStringList getDirs(); private: diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index ba08954a3c..a3e4d50d99 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -114,7 +114,7 @@ void TrackDAO::finish() { @return the track id for the track located at location, or -1 if the track is not in the database. */ -TrackId TrackDAO::getTrackId(const QString& absoluteFilePath) { +TrackId TrackDAO::getTrackId(const QString& absoluteFilePath) const { TrackId trackId; @@ -206,6 +206,37 @@ QString TrackDAO::getTrackLocation(TrackId trackId) { return trackLocation; } +QStringList TrackDAO::getTrackLocations(const QList<TrackId>& ids) { + QString stmt = + "SELECT track_locations.location FROM track_locations " + "INNER JOIN library on library.location=track_locations.id " + "WHERE library.id IN (%1)"; + { + QStringList idList; + idList.reserve(ids.size()); + for (const auto& id : ids) { + idList.append(id.toString()); + } + stmt = stmt.arg(idList.join(",")); + } + FwdSqlQuery query(m_database, stmt); + VERIFY_OR_DEBUG_ASSERT(!query.hasError()) { + return QStringList(); + } + if (!query.execPrepared()) { + return QStringList(); + } + QStringList locations; + locations.reserve(ids.size()); + const int locationColumn = query.record().indexOf("location"); + DEBUG_ASSERT(locationColumn >= 0); + while (query.next()) { + locations.append(query.fieldValue(locationColumn).toString()); + } + DEBUG_ASSERT(locations.size() <= ids.size()); + return locations; +} + void TrackDAO::saveTrack(Track* pTrack) { DEBUG_ASSERT(pTrack); if (pTrack->isDirty()) { @@ -268,18 +299,46 @@ void TrackDAO::slotTrackClean(Track* pTrack) { } void TrackDAO::databaseTrackAdded(TrackPointer pTrack) { - emit(dbTrackAdded(pTrack)); -} - -void TrackDAO::databaseTracksMoved(QSet<TrackId> tracksMovedSetOld, QSet<TrackId> tracksMovedSetNew) { - emit(tracksRemoved(tracksMovedSetNew)); - // results in a call of BaseTrackCache::updateTracksInIndex(trackIds); - emit(tracksAdded(tracksMovedSetOld)); + DEBUG_ASSERT(pTrack); + emit dbTrackAdded(pTrack); } void TrackDAO::databaseTracksChanged(QSet<TrackId> tracksChanged) { // results in a call of BaseTrackCache::updateTracksInIndex(trackIds); - emit(tracksAdded(tracksChanged)); + if (!tracksChanged.isEmpty()) { + emit tracksAdded(tracksChanged); + } +} + +void TrackDAO::databaseTracksReplaced(QList<QPair<TrackRef, TrackRef>> replacedTracks) { + QSet<TrackId> removedTrackIds; + QSet<TrackId> changedTrackIds; + for (const auto& replacedTrack : replacedTracks) { + const auto& removedTrackRef = replacedTrack.first; + const auto& changedTrackRef = replacedTrack.second; + DEBUG_ASSERT(removedTrackRef.getId().isValid()); + DEBUG_ASSERT(changedTrackRef.getId().isValid()); + // The (old)) location of the (re)moved track must be known! + DEBUG_ASSERT(!removedTrackRef.getLocation().isEmpty()); + // The (new) location of the changed track might be empty. + DEBUG_ASSERT(removedTrackRef.getLocation() != changedTrackRef.getLocation()); + changedTrackIds.insert(changedTrackRef.getId()); + // The ids might be identical if the same track has been only been + // relocated. In this case the track has not been removed. + if (removedTrackRef.getId() != changedTrackRef.getId()) { + // The id must also not match with any other changed track! + DEBUG_ASSERT(!changedTrackIds.contains(removedTrackRef.getId())); + removedTrackIds.insert(removedTrackRef.getId()); + } + } + DEBUG_ASSERT(removedTrackIds.size() <= changedTrackIds.size()); +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) + DEBUG_ASSERT(!removedTrackIds.intersects(changedTrackIds)); +#endif + if (!removedTrackIds.isEmpty()) { + emit tracksRemoved(removedTrackIds); + } + databaseTracksChanged(changedTrackIds); } void TrackDAO::slotTrackChanged(Track* pTrack) { @@ -828,11 +887,11 @@ void TrackDAO::afterUnhidingTracks( emit(tracksAdded(QSet<TrackId>::fromList(trackIds))); } -QList<TrackId> TrackDAO::getTrackIds(const QDir& dir) { +QList<TrackId> TrackDAO::getAllTrackIds(const QDir& rootDir) { // Capture entries that start with the directory prefix dir. // dir needs to end in a slash otherwise we might match other // directories. - const QString dirPath = dir.absolutePath(); + const QString dirPath = rootDir.absolutePath(); QString likeClause = SqlLikeWildcardEscaper::apply(dirPath + "/", kSqlLikeMatchAll) + kSqlLikeMatchAll; QSqlQuery query(m_database); @@ -1566,8 +1625,7 @@ namespace { // moved instead of being deleted outright, and so we can salvage your // existing metadata that you have in your DB (like cue points, etc.). // returns falls if canceled -bool TrackDAO::detectMovedTracks(QSet<TrackId>* pTracksMovedSetOld, - QSet<TrackId>* pTracksMovedSetNew, +bool TrackDAO::detectMovedTracks(QList<QPair<TrackRef, TrackRef>>* pReplacedTracks, const QStringList& addedTracks, volatile const bool* pCancel) { // This function should not start a transaction on it's own! @@ -1722,10 +1780,11 @@ bool TrackDAO::detectMovedTracks(QSet<TrackId>* pTracksMovedSetOld, } } - // We collect all the old tracks that has to be updated in BaseTrackCache - pTracksMovedSetOld->insert(oldTrackId); - // We collect collect all the new tracks the where added and deleted to BaseTrackCache - pTracksMovedSetNew->insert(newTrackId); + if (pReplacedTracks) { + auto oldTrackRef = TrackRef::fromFileInfo(oldTrackLocation, oldTrackId); + auto newTrackRef = TrackRef::fromFileInfo(newTrackLocation, newTrackId); + pReplacedTracks->append(qMakePair(oldTrackRef, newTrackRef)); + } } return true; } diff --git a/src/library/dao/trackdao.h b/src/library/dao/trackdao.h index c27c41e312..2d4bc8bb21 100644 --- a/src/library/dao/trackdao.h +++ b/src/library/dao/trackdao.h @@ -39,9 +39,9 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC } void finish(); - TrackId getTrackId(const QString& absoluteFilePath); + TrackId getTrackId(const QString& absoluteFilePath) const; QList<TrackId> getTrackIds(const QList<QFileInfo>& files); - QList<TrackId> getTrackIds(const QDir& dir); + QList<TrackId> getAllTrackIds(const QDir& rootDir); // WARNING: Only call this from the main thread instance of TrackDAO. TrackPointer getTrack(TrackId trackId) const; @@ -49,6 +49,7 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC // Returns a set of all track locations in the library. QSet<QString> getTrackLocations(); QString getTrackLocation(TrackId trackId); + QStringList getTrackLocations(const QList<TrackId>& trackIds); TrackPointer addSingleTrack(const TrackFile& trackFile, bool unremove); QList<TrackId> addMultipleTracks(const QList<QFileInfo>& fileInfoList, bool unremove); @@ -90,8 +91,7 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC void markTracksInDirectoriesAsVerified(const QStringList& directories); void invalidateTrackLocationsInLibrary(); void markUnverifiedTracksAsDeleted(); - bool detectMovedTracks(QSet<TrackId>* pTracksMovedSetOld, - QSet<TrackId>* pTracksMovedSetNew, + bool detectMovedTracks(QList<QPair<TrackRef, TrackRef>>* pReplacedTracks, const QStringList& addedTracks, volatile const bool* pCancel); @@ -117,8 +117,8 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC public slots: void databaseTrackAdded(TrackPointer pTrack); - void databaseTracksMoved(QSet<TrackId> tracksMovedSetOld, QSet<TrackId> tracksMovedSetNew); void databaseTracksChanged(QSet<TrackId> tracksChanged); + void databaseTracksReplaced(QList<QPair<TrackRef, TrackRef>> replacedTracks); private slots: void slotTrackDirty(Track* pTrack); diff --git a/src/library/dlghidden.cpp b/src/library/dlghidden.cpp index 3c9ed62bf3..162c1005ae 100644 --- a/src/library/dlghidden.cpp +++ b/src/library/dlghidden.cpp @@ -24,7 +24,7 @@ DlgHidden::DlgHidden(QWidget* parent, UserSettingsPointer pConfig, box->insertWidget(1, m_pTrackTableView); } - m_pHiddenTableModel = new HiddenTableModel(this, pTrackCollection); + m_pHiddenTableModel = new HiddenTableModel(this, pLibrary); m_pTrackTableView->loadTrackModel(m_pHiddenTableModel); connect(btnUnhide, diff --git a/src/library/dlgmissing.cpp b/src/library/dlgmissing.cpp index 5fc623d3a1..71627c4d5b 100644 --- a/src/library/dlgmissing.cpp +++ b/src/library/dlgmissing.cpp @@ -23,7 +23,7 @@ DlgMissing::DlgMissing(QWidget* parent, UserSettingsPointer pConfig, box->insertWidget(1, m_pTrackTableView); } - m_pMissingTableModel = new MissingTableModel(this, pTrackCollection); + m_pMissingTableModel = new MissingTableModel(this, pLibrary); m_pTrackTableView->loadTrackModel(m_pMissingTableModel); connect(btnPurge, &QPushButton::clicked, m_pTrackTableView, &WTrackTableView::slotPurge); diff --git a/src/library/externaltrackcollection.cpp b/src/library/externaltrackcollection.cpp new file mode 100644 index 0000000000..9157adbb93 --- /dev/null +++ b/src/library/externaltrackcollection.cpp @@ -0,0 +1,16 @@ +#include "library/externaltrackcollection.h" + + +void ExternalTrackCollection::deduplicateTracks( + const QList<DuplicateTrack>& duplicateTracks) { + QList<QString> purgedTracks; + QList<TrackRef> updatedTracks; + purgedTracks.reserve(duplicateTracks.size()); + updatedTracks.reserve(duplicateTracks.size()); + for (const auto& duplicateTrack : duplicateTracks) { + purgedTracks += duplicateTrack.removed.getLocation(); + updatedTracks += duplicateTrack.replacedBy; + } + purgeTracks(purgedTracks); + updateTracks(updatedTracks); +} diff --git a/src/library/externaltrackcollection.h b/src/library/externaltrackcollection.h new file mode 100644 index 0000000000..eeed8b5d42 --- /dev/null +++ b/src/library/externaltrackcollection.h @@ -0,0 +1,103 @@ +#pragma once + +#include <QDir> +#include <QList> +#include <QString> + +#include "track/trackref.h" + + +class Track; + +class LibraryFeature; + +// This interface and base class enable to synchronize external +// track collections with Mixxx. It provides methods that will +// be invoked by Mixxx after tracks have been added, modified or +// deleted in the internal track collection. It also notifies +// external track collections if the metadata of a single track +// has been saved. A track in the internal track collection always +// refers to a single, local file. +// +// All functions must be implemented in a non-blocking fashion, +// i.e. asynchronously. They will be invoked AFTER the corresponding +// operation has been executed on the internal track collection. +// +// WARNING: External track collections MUST NOT modify the track +// files while Mixxx is running to avoid file corruption caused by +// concurrent write access! +class ExternalTrackCollection : public QObject { +Q_OBJECT + + public: + virtual ~ExternalTrackCollection() = default; + + virtual QString name() const = 0; + + // Check if the connection to the extenal track collection + // has been established, i.e. if the synchronization is active. + virtual bool isActive() const = 0; + + // Synchronously (blocking) stop the synchronization by + // finishing all pending requests. + virtual void shutdown() {} + + // All tracks in the corresponding directory need to be + // relocated recursively by updating their location. + virtual /*async*/ void relocateDirectory( + const QString& oldRootDir, + const QString& newRootDir) = 0; + + // A (potentially large) number of tracks has recently been + // modified by a batch update in the internal track collection. + // The metadata of those tracks might need to be loaded in order + // to send it to the external track collection. + virtual /*async*/ void updateTracks( + const QList<TrackRef>& updatedTracks) = 0; + + // The tracks referenced by their (local) file path have been + // removed from the track collection and may also have disappeared + // from the file system. + virtual /*async*/ void purgeTracks( + const QList<QString>& trackLocations) = 0; + + // All tracks in the corresponding directory have been removed + // recursively from the root directory and the directory may + // have disappeared from the file system. + virtual /*async*/ void purgeAllTracks( + const QDir& rootDir) = 0; + + // Duplications have been resolved by removing the duplicate track + // and replacing any references with the corresponding replacement + // track. + // The default implementation first purges all duplicate tracks that + // have been removed and then updates all the replaced tracks. + struct DuplicateTrack { + TrackRef removed; + TrackRef replacedBy; + }; + virtual /*async*/ void deduplicateTracks( + const QList<DuplicateTrack>& duplicateTracks); + + // A new track has been added to the internal track collection or the + // modified metadata of an existing track has just been saved. + enum class ChangeHint { + Added, + Modified, + }; + virtual /*async*/ void saveTrack( + const Track& track, + ChangeHint changeHint) = 0; + + // Create the corresponding library feature (if desired) that will + // be hooked into the side pane in Mixxx. + virtual LibraryFeature* newLibraryFeature( + QObject* /*parent*/) { + return nullptr; + } + + protected: + explicit ExternalTrackCollection(QObject* parent = nullptr) + : QObject(parent) { + } +}; diff --git a/src/library/hiddentablemodel.cpp b/src/library/hiddentablemodel.cpp index bc62631b2d..dc3738617a 100644 --- a/src/library/hiddentablemodel.cpp +++ b/src/library/hiddentablemodel.cpp @@ -1,11 +1,13 @@ #include "library/hiddentablemodel.h" +#include "library/library.h" #include "library/dao/trackschema.h" HiddenTableModel::HiddenTableModel(QObject* parent, - TrackCollection* pTrackCollection) - : BaseSqlTableModel(parent, pTrackCollection, "mixxx.db.model.missing") { + Library* pLibrary) + : BaseSqlTableModel(parent, &pLibrary->trackCollection(), "mixxx.db.model.missing"), + m_pLibrary(pLibrary) { setTableModel(); } @@ -51,7 +53,7 @@ void HiddenTableModel::purgeTracks(const QModelIndexList& indices) { trackIds.append(getTrackId(index)); } - m_pTrackCollection->purgeTracks(trackIds); + m_pLibrary->purgeTracks(trackIds); // TODO(rryan) : do not select, instead route event to BTC and notify from // there. diff --git a/src/library/hiddentablemodel.h b/src/library/hiddentablemodel.h index 7551c4524c..9223613cbe 100644 --- a/src/library/hiddentablemodel.h +++ b/src/library/hiddentablemodel.h @@ -3,10 +3,12 @@ #include "library/basesqltablemodel.h" +class Library; + class HiddenTableModel : public BaseSqlTableModel { Q_OBJECT public: - HiddenTableModel(QObject* parent, TrackCollection* pTrackCollection); + HiddenTableModel(QObject* parent, Library* pLibrary); ~HiddenTableModel() final; void setTableModel(int id = -1); @@ -16,6 +18,9 @@ class HiddenTableModel : public BaseSqlTableModel { void unhideTracks(const QModelIndexList& indices) final; Qt::ItemFlags flags(const QModelIndex &index) const final; CapabilitiesFlags getCapabilities() const final; + + private: + Library* m_pLibrary; }; #endif diff --git a/src/library/library.cpp b/src/library/library.cpp index bd9dfa3376..e8225b4d43 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -40,6 +40,7 @@ #include "controllers/keyboard/keyboardeventfilter.h" +#include "library/externaltrackcollection.h" namespace { @@ -91,14 +92,38 @@ Library::Library( kLogger.info() << "Connecting database"; m_pTrackCollection->connectDatabase(dbConnection); +#if defined(__AOIDE__) + m_externalTrackCollections += new mixxx::aoide::TrackCollection(pConfig, m_pTrackCollection, this); +#endif + qRegisterMetaType<Library::RemovalType>("Library::RemovalType"); m_pKeyNotation.reset(new ControlObject(ConfigKey(kConfigGroup, "key_notation"))); - connect(&m_scanner, &LibraryScanner::scanStarted, this, &Library::scanStarted); - connect(&m_scanner, &LibraryScanner::scanFinished, this, &Library::scanFinished); - // Refresh the library models when the library (re)scan is finished. - connect(&m_scanner, &LibraryScanner::scanFinished, this, &Library::slotRefreshLibraryModels); + connect(&m_scanner, + &LibraryScanner::scanStarted, + this, + &Library::scanStarted); + connect(&m_scanner, + &LibraryScanner::scanFinished, + this, + &Library::scanFinished); + connect(&m_scanner, + &LibraryScanner::scanFinished, + this, + &Library::slotRefreshLibraryModels); + connect(&m_scanner, + &LibraryScanner::trackAdded, + this, + &Library::slotScanTrackAdded); + connect(&m_scanner, + &LibraryScanner::tracksChanged, + this, + &Library::slotScanTracksUpdated); + connect(&m_scanner, + &LibraryScanner::tracksReplaced, + this, + &Library::slotScanTracksReplaced); // TODO(rryan) -- turn this construction / adding of features into a static // method or something -- CreateDefaultLibrary @@ -164,6 +189,21 @@ Library::Library( addFeature(new TraktorFeature(this, m_pTrackCollection)); } + for (const auto& externalTrackCollection : m_externalTrackCollections) { + auto feature = externalTrackCollection->newLibraryFeature(this); + if (feature) { + kLogger.info() + << "Adding library feature for" + << externalTrackCollection->name(); + addFeature(feature); + } else { + kLogger.info() + << "Library feature for" + << externalTrackCollection->name() + << "is not available"; + } + } + // On startup we need to check if all of the user's library folders are // accessible to us. If the user is using a database from <1.12.0 with // sandboxing then we will need them to give us permission. @@ -203,6 +243,10 @@ Library::~Library() { delete m_pLibraryControl; + for (const auto& externalTrackCollection : m_externalTrackCollections) { + externalTrackCollection->shutdown(); + } + kLogger.info() << "Disconnecting database"; m_pTrackCollection->disconnectDatabase(); @@ -260,7 +304,12 @@ void Library::bindSidebarWidget(WLibrarySidebar* pSidebarWidget) { void Library::bindWidget(WLibrary* pLibraryWidget, KeyboardEventFilter* pKeyboard) { WTrackTableView* pTrackTableView = - new WTrackTableView(pLibraryWidget, m_pConfig, m_pTrackCollection); + new WTrackTableView( + pLibraryWidget, + m_pConfig, + m_pTrackCollection, + true, + m_externalTrackCollections); pTrackTableView->installEventFilter(pKeyboard); connect(this, &Library::showTrackModel, @@ -435,7 +484,7 @@ void Library::slotRequestAddDir(QString dir) { QDir directory(dir); Sandbox::createSecurityToken(directory); - if (!m_pTrackCollection->getDirectoryDAO().addDirectory(dir)) { + if (!m_pTrackCollection->addDirectory(dir)) { QMessageBox::information(0, tr("Add Directory to Library"), tr("Could not add the directory to your library. Either this " "directory is already in your library or you are currently " @@ -457,7 +506,7 @@ void Library::slotRequestRemoveDir(QString dir, RemovalType removalType) { break; case Library::PurgeTracks: // The user requested that we purge all metadata. - m_pTrackCollection->purgeTracks(dir); + purgeAllTracks(dir); break; case Library::LeaveTracksUnchanged: default: @@ -485,7 +534,7 @@ void Library::slotRequestRemoveDir(QString dir, RemovalType removalType) { } void Library::slotRequestRelocateDir(QString oldDir, QString newDir) { - m_pTrackCollection->relocateDirectory(oldDir, newDir); + relocateDirectory(oldDir, newDir); // also update the config file if necessary so that downgrading is still // possible @@ -529,8 +578,222 @@ void Library::saveCachedTrack(Track* pTrack) noexcept { // concurrently. m_pTrackCollection->exportTrackMetadata(pTrack); - // The track must be saved while the cache is locked to - // prevent that a new track is created from the outdated - // metadata that is is the database before saving is finished. + // Th dirty flag is reset while saving the track in the internal + // collection! + const bool trackDirty = pTrack->isDirty(); + + // This operation must be executed synchronously while the cache is + // locked to prevent that a new track is created from outdated + // metadata in the database before saving finished. + kLogger.debug() + << "Saving cached track" + << pTrack->getLocation() + << "in internal collection"; m_pTrackCollection->saveTrack(pTrack); + + if (m_externalTrackCollections.isEmpty()) { + return; + } + if (pTrack->getId().isValid()) { + // Track still exists in the internal collection/database + if (trackDirty) { + kLogger.debug() + << "Saving modified track" + << pTrack->getLocation() + << "in" + << m_externalTrackCollections.size() + << "external collection(s)"; + for (const auto& externalTrackCollection : m_externalTrackCollections) { + externalTrackCollection->saveTrack( + *pTrack, + ExternalTrackCollection::ChangeHint::Modified); + } + } + } else { + // Track has been deleted from the local internal collection/database + // while it was cached in-memory + kLogger.debug() + << "Purging deleted track" + << pTrack->getLocation() + << "from" + << m_externalTrackCollections.size() + << "external collection(s)"; + for (const auto& externalTrackCollection : m_externalTrackCollections) { + externalTrackCollection->purgeTracks( + QStringList{pTrack->getLocation()}); + } + } +} + +void Library::relocateDirectory(QString oldDir, QString newDir) { + kLogger.debug() + << "Relocating directory in internal track collection:" + << oldDir + << "->" + << newDir; + // TODO(XXX): Add error handling in TrackCollection::relocateDirectory() + m_pTrackCollection->relocateDirectory(oldDir, newDir); + if (m_externalTrackCollections.isEmpty()) { + return; + } + kLogger.debug() + << "Relocating directory in" + << m_externalTrackCollections.size() + << "external track collection(s):" + << oldDir + << "->" + << newDir; + for (const auto& externalTrackCollection : m_externalTrackCollections) { + externalTrackCollection->relocateDirectory(oldDir, newDir); + } +} + +void Library::purgeTracks(const QList<TrackId>& trackIds) { + if (trackIds.isEmpty()) { + return; + } + // Collect the corresponding track locations BEFORE purging the + // tracks from the internal collection! + QList<QString> trackLocations; + if (!m_externalTrackCollections.isEmpty()) { + trackLocations = + m_pTrackCollection->getTrackDAO().getTrackLocations(trackIds); + } + DEBUG_ASSERT(trackLocations.size() <= trackIds.size()); + kLogger.debug() + << "Purging" + << trackIds.size() + << "tracks from internal collection"; + if (!m_pTrackCollection->purgeTracks(trackIds)) { + kLogger.warning() |