summaryrefslogtreecommitdiffstats
path: root/src/library
diff options
context:
space:
mode:
authorUwe Klotz <uklotz@mixxx.org>2019-09-29 09:53:02 +0200
committerUwe Klotz <uklotz@mixxx.org>2019-09-29 09:53:02 +0200
commit1d882d8b5022005a120e23f20496f8cd9795d45c (patch)
tree5089fa8478860f26ec9e0a3f91f25269285bc76e /src/library
parenta1fdea2aef545ee0bd9f25027f0d66f936325cf8 (diff)
parent5998ee24e30361b3d14907d899e191a0aee16a3b (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.cpp30
-rw-r--r--src/library/dao/directorydao.h2
-rw-r--r--src/library/dao/trackdao.cpp93
-rw-r--r--src/library/dao/trackdao.h10
-rw-r--r--src/library/dlghidden.cpp2
-rw-r--r--src/library/dlgmissing.cpp2
-rw-r--r--src/library/externaltrackcollection.cpp16
-rw-r--r--src/library/externaltrackcollection.h103
-rw-r--r--src/library/hiddentablemodel.cpp8
-rw-r--r--src/library/hiddentablemodel.h7
-rw-r--r--src/library/library.cpp285
-rw-r--r--src/library/library.h13
-rw-r--r--src/library/locationdelegate.cpp23
-rw-r--r--src/library/locationdelegate.h23
-rw-r--r--src/library/missingtablemodel.cpp15
-rw-r--r--src/library/missingtablemodel.h7
-rw-r--r--src/library/scanner/libraryscanner.cpp43
-rw-r--r--src/library/scanner/libraryscanner.h2
-rw-r--r--src/library/stareditor.cpp5
-rw-r--r--src/library/tableitemdelegate.cpp36
-rw-r--r--src/library/tableitemdelegate.h23
-rw-r--r--src/library/trackcollection.cpp37
-rw-r--r--src/library/trackcollection.h12
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()