diff --git a/CMakeLists.txt b/CMakeLists.txt index 29b6ec35088..da9c4832873 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -832,7 +832,6 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/track/serato/markers2.cpp src/track/serato/tags.cpp src/track/track.cpp - src/track/trackfile.cpp src/track/trackinfo.cpp src/track/trackmetadata.cpp src/track/tracknumbers.cpp diff --git a/src/engine/cachingreader/cachingreaderworker.cpp b/src/engine/cachingreader/cachingreaderworker.cpp index 5353b7b1058..3a9fd507a21 100644 --- a/src/engine/cachingreader/cachingreaderworker.cpp +++ b/src/engine/cachingreader/cachingreaderworker.cpp @@ -135,17 +135,16 @@ void CachingReaderWorker::loadTrack(const TrackPointer& pTrack) { // Emit that a new track is loading, stops the current track emit trackLoading(); - const QString trackLocation = pTrack->getLocation(); - if (trackLocation.isEmpty() || !pTrack->checkFileExists()) { + if (!pTrack->getFileInfo().checkFileExists()) { kLogger.warning() << m_group << "File not found" - << trackLocation; + << pTrack->getFileInfo(); const auto update = ReaderStatusUpdate::trackUnloaded(); m_pReaderStatusFIFO->writeBlocking(&update, 1); emit trackLoadFailed(pTrack, tr("The file '%1' could not be found.") - .arg(QDir::toNativeSeparators(trackLocation))); + .arg(QDir::toNativeSeparators(pTrack->getLocation()))); return; } @@ -156,12 +155,12 @@ void CachingReaderWorker::loadTrack(const TrackPointer& pTrack) { kLogger.warning() << m_group << "Failed to open file" - << trackLocation; + << pTrack->getFileInfo(); const auto update = ReaderStatusUpdate::trackUnloaded(); m_pReaderStatusFIFO->writeBlocking(&update, 1); emit trackLoadFailed(pTrack, tr("The file '%1' could not be loaded.") - .arg(QDir::toNativeSeparators(trackLocation))); + .arg(QDir::toNativeSeparators(pTrack->getLocation()))); return; } @@ -173,12 +172,12 @@ void CachingReaderWorker::loadTrack(const TrackPointer& pTrack) { kLogger.warning() << m_group << "Failed to open empty file" - << trackLocation; + << pTrack->getFileInfo(); const auto update = ReaderStatusUpdate::trackUnloaded(); m_pReaderStatusFIFO->writeBlocking(&update, 1); emit trackLoadFailed(pTrack, tr("The file '%1' is empty and could not be loaded.") - .arg(QDir::toNativeSeparators(trackLocation))); + .arg(QDir::toNativeSeparators(pTrack->getLocation()))); return; } diff --git a/src/library/autodj/autodjfeature.cpp b/src/library/autodj/autodjfeature.cpp index a732951387a..de985538a44 100644 --- a/src/library/autodj/autodjfeature.cpp +++ b/src/library/autodj/autodjfeature.cpp @@ -256,7 +256,7 @@ void AutoDJFeature::slotAddRandomTrack() { << randomTrackId; continue; } - if (!pRandomTrack->checkFileExists()) { + if (!pRandomTrack->getFileInfo().checkFileExists()) { qWarning() << "Track does not exist:" << pRandomTrack->getInfo() << pRandomTrack->getFileInfo(); diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 0c41bdd375c..a39682797af 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -860,7 +860,7 @@ TrackPointer AutoDJProcessor::getNextTrackFromQueue() { m_pAutoDJTableModel->index(0, 0)); if (nextTrack) { - if (nextTrack->checkFileExists()) { + if (nextTrack->getFileInfo().checkFileExists()) { return nextTrack; } else { // Remove missing song from auto DJ playlist. diff --git a/src/library/banshee/bansheefeature.cpp b/src/library/banshee/bansheefeature.cpp index d7b565ec02f..e8ee1580aae 100644 --- a/src/library/banshee/bansheefeature.cpp +++ b/src/library/banshee/bansheefeature.cpp @@ -79,7 +79,9 @@ void BansheeFeature::activate() { qDebug() << m_databaseFile << "does not exist"; } - if (!Sandbox::askForAccess(m_databaseFile) || !m_connection.open(m_databaseFile)) { + mixxx::FileInfo fileInfo(m_databaseFile); + if (!Sandbox::askForAccess(&fileInfo) || + !m_connection.open(m_databaseFile)) { QMessageBox::warning( nullptr, tr("Error loading Banshee database"), diff --git a/src/library/banshee/bansheeplaylistmodel.cpp b/src/library/banshee/bansheeplaylistmodel.cpp index f0eca64c685..0630fed8ccf 100644 --- a/src/library/banshee/bansheeplaylistmodel.cpp +++ b/src/library/banshee/bansheeplaylistmodel.cpp @@ -225,7 +225,7 @@ TrackId BansheePlaylistModel::doGetTrackId(const TrackPointer& pTrack) const { if (pTrack) { for (int row = 0; row < rowCount(); ++row) { const QUrl rowUrl(getFieldString(index(row, 0), CLM_URI)); - if (TrackFile::fromUrl(rowUrl) == pTrack->getFileInfo()) { + if (mixxx::FileInfo::fromQUrl(rowUrl) == pTrack->getFileInfo()) { return TrackId(getFieldVariant(index(row, 0), CLM_VIEW_ORDER)); } } @@ -253,7 +253,7 @@ TrackPointer BansheePlaylistModel::getTrack(const QModelIndex& index) const { bool track_already_in_library = false; TrackPointer pTrack = m_pTrackCollectionManager->getOrAddTrack( - TrackRef::fromFileInfo(location), + TrackRef::fromFilePath(location), &track_already_in_library); // If this track was not in the Mixxx library it is now added and will be @@ -295,7 +295,7 @@ QString BansheePlaylistModel::getTrackLocation(const QModelIndex& index) const { } QUrl url(getFieldString(index, CLM_URI)); - QString location = TrackFile::fromUrl(url).location(); + QString location = mixxx::FileInfo::fromQUrl(url).location(); qDebug() << location << " = " << url; if (!location.isEmpty()) { return location; diff --git a/src/library/baseexternalplaylistmodel.cpp b/src/library/baseexternalplaylistmodel.cpp index 30762d468a8..8f574bb0d30 100644 --- a/src/library/baseexternalplaylistmodel.cpp +++ b/src/library/baseexternalplaylistmodel.cpp @@ -34,7 +34,7 @@ TrackPointer BaseExternalPlaylistModel::getTrack(const QModelIndex& index) const bool track_already_in_library = false; TrackPointer pTrack = m_pTrackCollectionManager->getOrAddTrack( - TrackRef::fromFileInfo(location), + TrackRef::fromFilePath(location), &track_already_in_library); // If this track was not in the Mixxx library it is now added and will be diff --git a/src/library/baseexternaltrackmodel.cpp b/src/library/baseexternaltrackmodel.cpp index 1f4649e3332..6a0539c6ffd 100644 --- a/src/library/baseexternaltrackmodel.cpp +++ b/src/library/baseexternaltrackmodel.cpp @@ -59,7 +59,7 @@ TrackPointer BaseExternalTrackModel::getTrack(const QModelIndex& index) const { bool track_already_in_library = false; TrackPointer pTrack = m_pTrackCollectionManager->getOrAddTrack( - TrackRef::fromFileInfo(location), + TrackRef::fromFilePath(location), &track_already_in_library); if (pTrack) { diff --git a/src/library/basesqltablemodel.cpp b/src/library/basesqltablemodel.cpp index a5336bdaa07..011011b7bf3 100644 --- a/src/library/basesqltablemodel.cpp +++ b/src/library/basesqltablemodel.cpp @@ -842,8 +842,8 @@ QList BaseSqlTableModel::getTrackRefs( QList trackRefs; trackRefs.reserve(indices.size()); foreach (QModelIndex index, indices) { - trackRefs.append(TrackRef::fromFileInfo( - TrackFile(getTrackLocation(index)), + trackRefs.append(TrackRef::fromFilePath( + getTrackLocation(index), getTrackId(index))); } return trackRefs; diff --git a/src/library/basetrackcache.cpp b/src/library/basetrackcache.cpp index d0a56945357..d3fcdd686f3 100644 --- a/src/library/basetrackcache.cpp +++ b/src/library/basetrackcache.cpp @@ -17,10 +17,10 @@ constexpr bool sDebug = false; } // namespace BaseTrackCache::BaseTrackCache(TrackCollection* pTrackCollection, - const QString& tableName, - const QString& idColumn, - const QStringList& columns, - bool isCaching) + const QString& tableName, + const QString& idColumn, + const QStringList& columns, + bool isCaching) : m_tableName(tableName), m_idColumn(idColumn), m_columnCount(columns.size()), diff --git a/src/library/basetracktablemodel.cpp b/src/library/basetracktablemodel.cpp index 986d66f3b6d..957068bb2b8 100644 --- a/src/library/basetracktablemodel.cpp +++ b/src/library/basetracktablemodel.cpp @@ -915,7 +915,7 @@ QList BaseTrackTableModel::collectUrls( continue; } visitedRows.insert(index.row()); - QUrl url = TrackFile(getTrackLocation(index)).toUrl(); + QUrl url = mixxx::FileInfo(getTrackLocation(index)).toQUrl(); if (url.isValid()) { urls.append(url); } diff --git a/src/library/browse/browsefeature.cpp b/src/library/browse/browsefeature.cpp index 560dcf86004..e69e39b01a8 100644 --- a/src/library/browse/browsefeature.cpp +++ b/src/library/browse/browsefeature.cpp @@ -253,17 +253,18 @@ void BrowseFeature::activateChild(const QModelIndex& index) { } else { // Open a security token for this path and if we do not have access, ask // for it. - auto dir = mixxx::FileAccess(mixxx::FileInfo(path)); - if (!dir.isReadable()) { - if (Sandbox::askForAccess(path)) { + auto dirInfo = mixxx::FileInfo(path); + auto dirAccess = mixxx::FileAccess(dirInfo); + if (!dirAccess.isReadable()) { + if (Sandbox::askForAccess(&dirInfo)) { // Re-create to get a new token. - dir = mixxx::FileAccess(mixxx::FileInfo(path)); + dirAccess = mixxx::FileAccess(dirInfo); } else { // TODO(rryan): Activate an info page about sandboxing? return; } } - m_browseModel.setPath(std::move(dir)); + m_browseModel.setPath(std::move(dirAccess)); } emit showTrackModel(&m_proxyModel); emit enableCoverArtDisplay(false); @@ -485,12 +486,12 @@ QStringList BrowseFeature::getDefaultQuickLinks() const { bool osDesktopDirIncluded = false; bool osDocumentsDirIncluded = false; const auto rootDirs = m_pLibrary->trackCollections()->internalCollection()->loadRootDirs(); - for (const mixxx::FileInfo& fileInfo : rootDirs) { - const auto dir = fileInfo.toQDir(); + for (mixxx::FileInfo fileInfo : rootDirs) { // Skip directories we don't have permission to. - if (!Sandbox::canAccessDir(dir)) { + if (!Sandbox::canAccess(&fileInfo)) { continue; } + const auto dir = fileInfo.toQDir(); if (dir == osMusicDir) { osMusicDirIncluded = true; } @@ -515,13 +516,11 @@ QStringList BrowseFeature::getDefaultQuickLinks() const { result << osDownloadsDir.canonicalPath() + "/"; } - if (!osDesktopDirIncluded && - Sandbox::canAccessDir(osDesktopDir)) { + if (!osDesktopDirIncluded && Sandbox::canAccessDir(osDesktopDir)) { result << osDesktopDir.canonicalPath() + "/"; } - if (!osDocumentsDirIncluded && - Sandbox::canAccessDir(osDocumentsDir)) { + if (!osDocumentsDirIncluded && Sandbox::canAccessDir(osDocumentsDir)) { result << osDocumentsDir.canonicalPath() + "/"; } diff --git a/src/library/browse/browsetablemodel.cpp b/src/library/browse/browsetablemodel.cpp index e51063a7ada..e35f805079f 100644 --- a/src/library/browse/browsetablemodel.cpp +++ b/src/library/browse/browsetablemodel.cpp @@ -19,7 +19,6 @@ #include "moc_browsetablemodel.cpp" #include "track/track.h" #include "util/compatibility.h" -#include "util/fileaccess.h" #include "widget/wlibrarytableview.h" BrowseTableModel::BrowseTableModel(QObject* parent, @@ -191,7 +190,7 @@ void BrowseTableModel::setPath(mixxx::FileAccess path) { } TrackPointer BrowseTableModel::getTrack(const QModelIndex& index) const { - return getTrackByRef(TrackRef::fromFileInfo(getTrackLocation(index))); + return getTrackByRef(TrackRef::fromFilePath(getTrackLocation(index))); } TrackPointer BrowseTableModel::getTrackByRef(const TrackRef& trackRef) const { @@ -298,7 +297,7 @@ QMimeData* BrowseTableModel::mimeData(const QModelIndexList& indexes) const { if (index.isValid()) { if (!rows.contains(index.row())) { rows.push_back(index.row()); - QUrl url = TrackFile(getTrackLocation(index)).toUrl(); + QUrl url = mixxx::FileInfo(getTrackLocation(index)).toQUrl(); if (!url.isValid()) { qDebug() << "ERROR invalid url" << url; continue; diff --git a/src/library/browse/browsethread.cpp b/src/library/browse/browsethread.cpp index 285053aa03e..e1ab90320af 100644 --- a/src/library/browse/browsethread.cpp +++ b/src/library/browse/browsethread.cpp @@ -116,8 +116,14 @@ void BrowseThread::populateModel() { BrowseTableModel* thisModelObserver = m_model_observer; m_path_mutex.unlock(); + if (!thisPath.info().hasLocation()) { + // Abort if the location is inaccessible or does not exist + qWarning() << "Skipping" << thisPath.info(); + return; + } + // Refresh the name filters in case we loaded new SoundSource plugins. - QStringList nameFilters(SoundSourceProxy::getSupportedFileNamePatterns()); + const QStringList nameFilters = SoundSourceProxy::getSupportedFileNamePatterns(); QDirIterator fileIt(thisPath.info().location(), nameFilters, @@ -151,12 +157,12 @@ void BrowseThread::populateModel() { item->setData("0", Qt::UserRole); row_data.insert(COLUMN_PREVIEW, item); - const QString filepath = fileIt.next(); + const auto fileAccess = mixxx::FileAccess( + mixxx::FileInfo(fileIt.next()), + thisPath.token()); { const TrackPointer pTrack = - SoundSourceProxy::importTemporaryTrack( - filepath, - thisPath.token()); + SoundSourceProxy::importTemporaryTrack(fileAccess); item = new QStandardItem(pTrack->getFileInfo().fileName()); item->setToolTip(item->text()); @@ -249,7 +255,7 @@ void BrowseThread::populateModel() { row_data.insert(COLUMN_GROUPING, item); const auto fileLastModified = - pTrack->getFileInfo().fileLastModified(); + fileAccess.info().lastModified(); item = new QStandardItem( mixxx::displayLocalDateTime(fileLastModified)); item->setToolTip(item->text()); @@ -257,7 +263,7 @@ void BrowseThread::populateModel() { row_data.insert(COLUMN_FILE_MODIFIED_TIME, item); const auto fileCreated = - pTrack->getFileInfo().fileCreated(); + fileAccess.info().birthTime(); item = new QStandardItem( mixxx::displayLocalDateTime(fileCreated)); item->setToolTip(item->text()); @@ -279,7 +285,7 @@ void BrowseThread::populateModel() { if (row % 10 == 0) { // this is a blocking operation emit rowsAppended(rows, thisModelObserver); - qDebug() << "Append " << rows.count() << " from " << filepath; + qDebug() << "Append " << rows.count() << " from " << fileAccess.info(); rows.clear(); } // Sleep additionally for 10ms which prevents us from GUI freezes diff --git a/src/library/coverart.cpp b/src/library/coverart.cpp index 612e413073a..e6447bf42b6 100644 --- a/src/library/coverart.cpp +++ b/src/library/coverart.cpp @@ -81,10 +81,6 @@ bool operator==(const CoverInfoRelative& lhs, const CoverInfoRelative& rhs) { lhs.coverLocation == rhs.coverLocation; } -bool operator!=(const CoverInfoRelative& lhs, const CoverInfoRelative& rhs) { - return !(lhs == rhs); -} - QDebug operator<<(QDebug dbg, const CoverInfoRelative& info) { const QDebugStateSaver saver(dbg); dbg = dbg.maybeSpace() << "CoverInfoRelative"; @@ -112,10 +108,11 @@ CoverInfo::LoadedImage CoverInfo::loadImage( loadedImage.result = LoadedImage::Result::ErrorMetadataWithEmptyTrackLocation; return loadedImage; } - loadedImage.filePath = trackLocation; + loadedImage.location = trackLocation; loadedImage.image = CoverArtUtils::extractEmbeddedCover( - TrackFile(trackLocation), - pTrackLocationToken); + mixxx::FileAccess( + mixxx::FileInfo(trackLocation), + pTrackLocationToken)); if (loadedImage.image.isNull()) { // TODO: extractEmbeddedCover() should indicate if no image // is available or if loading the embedded image failed. @@ -126,7 +123,7 @@ CoverInfo::LoadedImage CoverInfo::loadImage( loadedImage.result = LoadedImage::Result::Ok; } } else if (type == CoverInfo::FILE) { - auto coverFile = QFileInfo(coverLocation); + auto coverFile = mixxx::FileInfo(coverLocation); if (coverFile.isRelative()) { VERIFY_OR_DEBUG_ASSERT(!trackLocation.isEmpty()) { // This is not expected to happen, because every track @@ -138,23 +135,21 @@ CoverInfo::LoadedImage CoverInfo::loadImage( return loadedImage; } // Compose track directory with relative path - const auto trackFile = TrackFile(trackLocation); - DEBUG_ASSERT(trackFile.asFileInfo().isAbsolute()); - coverFile = QFileInfo( - trackFile.directory(), + const auto fileInfo = mixxx::FileInfo(trackLocation); + coverFile = mixxx::FileInfo( + fileInfo.locationPath(), coverLocation); } - DEBUG_ASSERT(coverFile.isAbsolute()); - loadedImage.filePath = coverFile.filePath(); + loadedImage.location = coverFile.location(); if (!coverFile.exists()) { loadedImage.result = LoadedImage::Result::ErrorFilePathDoesNotExist; return loadedImage; } SecurityTokenPointer pToken = Sandbox::openSecurityToken( - coverFile, + &coverFile, true); - if (loadedImage.image.load(loadedImage.filePath)) { + if (loadedImage.image.load(loadedImage.location)) { DEBUG_ASSERT(!loadedImage.image.isNull()); loadedImage.result = LoadedImage::Result::Ok; } else { @@ -242,7 +237,7 @@ QDebug operator<<(QDebug dbg, const CoverInfo::LoadedImage& loadedImage) { dbg = dbg.maybeSpace() << "CoverInfo::LoadedImage"; return dbg.nospace() << '{' - << loadedImage.filePath + << loadedImage.location << ',' << loadedImage.image.size() << ',' diff --git a/src/library/coverart.h b/src/library/coverart.h index 9839641d9fe..69ae91e55d2 100644 --- a/src/library/coverart.h +++ b/src/library/coverart.h @@ -120,7 +120,11 @@ class CoverInfoRelative { }; bool operator==(const CoverInfoRelative& lhs, const CoverInfoRelative& rhs); -bool operator!=(const CoverInfoRelative& lhs, const CoverInfoRelative& rhs); + +inline bool operator!=(const CoverInfoRelative& lhs, const CoverInfoRelative& rhs) { + return !(lhs == rhs); +} + QDebug operator<<(QDebug dbg, const CoverInfoRelative& info); class CoverInfo : public CoverInfoRelative { @@ -158,8 +162,8 @@ class CoverInfo : public CoverInfoRelative { QImage image; /// Either the track location if the image was embedded in - /// the metadata or the (absolute) path of the image file. - QString filePath; + /// the metadata or the location of the image file. + QString location; /// The result of the operation. Result result; diff --git a/src/library/coverartcache.cpp b/src/library/coverartcache.cpp index 54152645e7f..457e61bf0bc 100644 --- a/src/library/coverartcache.cpp +++ b/src/library/coverartcache.cpp @@ -182,7 +182,7 @@ CoverArtCache::FutureResult CoverArtCache::loadCover( DEBUG_ASSERT(!res.coverInfoUpdated); auto loadedImage = coverInfo.loadImage( - pTrack ? pTrack->getSecurityToken() : SecurityTokenPointer()); + pTrack ? pTrack->getFileAccess().token() : SecurityTokenPointer()); if (!loadedImage.image.isNull()) { // Refresh hash before resizing the original image! res.coverInfoUpdated = coverInfo.refreshImageDigest(loadedImage.image); @@ -228,7 +228,7 @@ void CoverArtCache::coverLoaded() { QPixmap pixmap; if (res.coverArt.loadedImage.result != CoverInfo::LoadedImage::Result::NoImage) { if (res.coverArt.loadedImage.result == CoverInfo::LoadedImage::Result::Ok) { - DEBUG_ASSERT(!res.coverArt.loadedImage.filePath.isEmpty()); + DEBUG_ASSERT(!res.coverArt.loadedImage.location.isEmpty()); } else { DEBUG_ASSERT(res.coverArt.loadedImage.image.isNull()); kLogger.warning() diff --git a/src/library/coverartdelegate.cpp b/src/library/coverartdelegate.cpp index b68a452e02d..e43965c0a0b 100644 --- a/src/library/coverartdelegate.cpp +++ b/src/library/coverartdelegate.cpp @@ -107,7 +107,7 @@ TrackPointer CoverArtDelegate::loadTrackByLocation( return TrackPointer(); } return m_pTrackModel->getTrackByRef( - TrackRef::fromFileInfo(trackLocation)); + TrackRef::fromFilePath(trackLocation)); } void CoverArtDelegate::paintItem( diff --git a/src/library/coverartutils.cpp b/src/library/coverartutils.cpp index da948a7969c..2c244ddb3fa 100644 --- a/src/library/coverartutils.cpp +++ b/src/library/coverartutils.cpp @@ -10,7 +10,6 @@ #include "util/logger.h" #include "util/regex.h" - namespace { mixxx::Logger kLogger("CoverArtUtils"); @@ -29,7 +28,11 @@ QString CoverArtUtils::defaultCoverLocation() { //static QStringList CoverArtUtils::supportedCoverArtExtensions() { QStringList extensions; - extensions << "jpg" << "jpeg" << "png" << "gif" << "bmp"; + extensions << "jpg" + << "jpeg" + << "png" + << "gif" + << "bmp"; return extensions; } @@ -41,19 +44,17 @@ QString CoverArtUtils::supportedCoverArtExtensionsRegex() { //static QImage CoverArtUtils::extractEmbeddedCover( - TrackFile trackFile, - SecurityTokenPointer pToken) { - return SoundSourceProxy::importTemporaryCoverImage( - std::move(trackFile), std::move(pToken)); + mixxx::FileAccess trackFileAccess) { + return SoundSourceProxy::importTemporaryCoverImage(std::move(trackFileAccess)); } //static QList CoverArtUtils::findPossibleCoversInFolder(const QString& folder) { // Search for image files in the track directory. QRegExp coverArtFilenames(supportedCoverArtExtensionsRegex(), - Qt::CaseInsensitive); + Qt::CaseInsensitive); QDirIterator it(folder, - QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); + QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); QFile currentFile; QFileInfo currentFileInfo; QList possibleCovers; @@ -61,7 +62,7 @@ QList CoverArtUtils::findPossibleCoversInFolder(const QString& folder it.next(); currentFileInfo = it.fileInfo(); if (currentFileInfo.isFile() && - coverArtFilenames.indexIn(currentFileInfo.fileName()) != -1) { + coverArtFilenames.indexIn(currentFileInfo.fileName()) != -1) { possibleCovers.append(currentFileInfo); } } @@ -80,7 +81,7 @@ CoverInfoRelative CoverArtUtils::selectCoverArtForTrack( //static CoverInfoRelative CoverArtUtils::selectCoverArtForTrack( - const TrackFile& trackFile, + const mixxx::FileInfo& trackFile, const QString& albumName, const QList& covers) { CoverInfoRelative coverInfoRelative; @@ -109,34 +110,34 @@ CoverInfoRelative CoverArtUtils::selectCoverArtForTrack( foreach (const QFileInfo& file, covers) { const QString coverBaseName = file.baseName(); if (bestType > TRACK_BASENAME && - coverBaseName.compare(trackFile.baseName(), - Qt::CaseInsensitive) == 0) { + coverBaseName.compare(trackFile.baseName(), + Qt::CaseInsensitive) == 0) { bestInfo = &file; // This is the best type (TRACK_BASENAME) so we know we're done. break; } else if (bestType > ALBUM_NAME && - coverBaseName.compare(albumName, - Qt::CaseInsensitive) == 0) { + coverBaseName.compare(albumName, + Qt::CaseInsensitive) == 0) { bestType = ALBUM_NAME; bestInfo = &file; } else if (bestType > COVER && - coverBaseName.compare(QLatin1String("cover"), - Qt::CaseInsensitive) == 0) { + coverBaseName.compare(QLatin1String("cover"), + Qt::CaseInsensitive) == 0) { bestType = COVER; bestInfo = &file; } else if (bestType > FRONT && - coverBaseName.compare(QLatin1String("front"), - Qt::CaseInsensitive) == 0) { + coverBaseName.compare(QLatin1String("front"), + Qt::CaseInsensitive) == 0) { bestType = FRONT; bestInfo = &file; } else if (bestType > ALBUM && - coverBaseName.compare(QLatin1String("album"), - Qt::CaseInsensitive) == 0) { + coverBaseName.compare(QLatin1String("album"), + Qt::CaseInsensitive) == 0) { bestType = ALBUM; bestInfo = &file; } else if (bestType > FOLDER && - coverBaseName.compare(QLatin1String("folder"), - Qt::CaseInsensitive) == 0) { + coverBaseName.compare(QLatin1String("folder"), + Qt::CaseInsensitive) == 0) { bestType = FOLDER; bestInfo = &file; } @@ -156,7 +157,7 @@ CoverInfoRelative CoverArtUtils::selectCoverArtForTrack( } CoverInfoRelative CoverInfoGuesser::guessCoverInfo( - const TrackFile& trackFile, + const mixxx::FileInfo& trackFile, const QString& albumName, const QImage& embeddedCover) { if (!embeddedCover.isNull()) { @@ -168,9 +169,9 @@ CoverInfoRelative CoverInfoGuesser::guessCoverInfo( return coverInfo; } - const auto trackFolder = trackFile.directory(); + const auto trackFolder = trackFile.locationPath(); if (trackFolder != m_cachedFolder) { - m_cachedFolder = trackFile.directory(); + m_cachedFolder = trackFile.locationPath(); m_cachedPossibleCoversInFolder = CoverArtUtils::findPossibleCoversInFolder( m_cachedFolder); @@ -183,18 +184,16 @@ CoverInfoRelative CoverInfoGuesser::guessCoverInfo( CoverInfoRelative CoverInfoGuesser::guessCoverInfoForTrack( const Track& track) { - const auto trackFile = track.getFileInfo(); + const auto fileAccess = track.getFileAccess(); if (kLogger.debugEnabled()) { kLogger.debug() << "Guessing cover art for track" - << trackFile; + << fileAccess.info(); } return guessCoverInfo( - trackFile, + fileAccess.info(), track.getAlbum(), - CoverArtUtils::extractEmbeddedCover( - trackFile, - track.getSecurityToken())); + CoverArtUtils::extractEmbeddedCover(fileAccess)); } void CoverInfoGuesser::guessAndSetCoverInfoForTrack( diff --git a/src/library/coverartutils.h b/src/library/coverartutils.h index e6e0ca96739..fb858f2b835 100644 --- a/src/library/coverartutils.h +++ b/src/library/coverartutils.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -9,12 +8,17 @@ #include "track/track_decl.h" #include "util/cache.h" +#include "util/fileinfo.h" #include "util/imageutils.h" -#include "util/sandbox.h" class CoverInfo; class CoverInfoRelative; -class TrackFile; + +namespace mixxx { + +class FileAccess; + +} // namespace mixxx class CoverArtUtils { public: @@ -24,8 +28,7 @@ class CoverArtUtils { // Extracts the first cover art image embedded within the file. static QImage extractEmbeddedCover( - TrackFile trackFile, - SecurityTokenPointer pToken); + mixxx::FileAccess trackFileAccess); static QStringList supportedCoverArtExtensions(); static QString supportedCoverArtExtensionsRegex(); @@ -56,7 +59,7 @@ class CoverArtUtils { // files. Assumes a SecurityTokenPointer is held by the caller for all files // in 'covers'. static CoverInfoRelative selectCoverArtForTrack( - const TrackFile& trackFile, + const mixxx::FileInfo& trackFile, const QString& albumName, const QList& covers); }; @@ -68,7 +71,7 @@ class CoverInfoGuesser { // Guesses the cover art for the provided track. // An embedded cover must be extracted beforehand and provided. CoverInfoRelative guessCoverInfo( - const TrackFile& trackFile, + const mixxx::FileInfo& trackFile, const QString& albumName, const QImage& embeddedCover); diff --git a/src/library/dao/directorydao.cpp b/src/library/dao/directorydao.cpp index b94d7102822..fe36cf23d6e 100644 --- a/src/library/dao/directorydao.cpp +++ b/src/library/dao/directorydao.cpp @@ -186,13 +186,13 @@ QList DirectoryDAO::relocateDirectory( loc_ids.append(DbId(query.value(1).toInt())); auto trackId = TrackId(query.value(0)); auto oldLocation = query.value(2).toString(); - auto missingTrackRef = TrackRef::fromFileInfo( - TrackFile(oldLocation), + auto missingTrackRef = TrackRef::fromFilePath( + oldLocation, std::move(trackId)); const int oldSuffixLen = oldLocation.size() - oldFolder.size(); QString newLocation = newFolder + oldLocation.right(oldSuffixLen); - auto addedTrackRef = TrackRef::fromFileInfo( - TrackFile(newLocation) /*without TrackId*/); + auto addedTrackRef = TrackRef::fromFilePath( + newLocation /*without TrackId*/); relocatedTracks.append(RelocatedTrack( std::move(missingTrackRef), std::move(addedTrackRef))); diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index a811b8dd536..c699164dca9 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -156,10 +156,10 @@ TrackId TrackDAO::getTrackIdByLocation(const QString& location) const { } QList TrackDAO::resolveTrackIds( - const QList& trackFiles, + const QList& fileInfos, ResolveTrackIdFlags flags) { QList trackIds; - trackIds.reserve(trackFiles.size()); + trackIds.reserve(fileInfos.size()); // Create a temporary database of the paths of all the imported tracks. QSqlQuery query(m_database); @@ -172,9 +172,9 @@ QList TrackDAO::resolveTrackIds( } QStringList pathList; - pathList.reserve(trackFiles.size()); - for (const auto& trackFile: trackFiles) { - pathList << "(" + SqlStringFormatter::format(m_database, trackFile.location()) + ")"; + pathList.reserve(fileInfos.size()); + for (const auto& fileInfo : fileInfos) { + pathList << "(" + SqlStringFormatter::format(m_database, fileInfo.location()) + ")"; } // Add all the track paths temporary to this database. @@ -227,13 +227,13 @@ QList TrackDAO::resolveTrackIds( while (query.next()) { trackIds.append(TrackId(query.value(idColumn))); } - DEBUG_ASSERT(trackIds.size() <= trackFiles.size()); - if (trackIds.size() < trackFiles.size()) { + DEBUG_ASSERT(trackIds.size() <= fileInfos.size()); + if (trackIds.size() < fileInfos.size()) { qDebug() << "TrackDAO::getTrackIds(): Found only" - << trackIds.size() - << "of" - << trackFiles.size() - << "tracks in library"; + << trackIds.size() + << "of" + << fileInfos.size() + << "tracks in library"; } } else { LOG_FAILED_QUERY(query); @@ -495,19 +495,19 @@ namespace { bool insertTrackLocation( QSqlQuery* pTrackLocationInsert, - const TrackFile& trackFile) { + const mixxx::FileInfo& fileInfo) { DEBUG_ASSERT(pTrackLocationInsert); - pTrackLocationInsert->bindValue(":location", trackFile.location()); - pTrackLocationInsert->bindValue(":directory", trackFile.directory()); - pTrackLocationInsert->bindValue(":filename", trackFile.fileName()); - pTrackLocationInsert->bindValue(":filesize", trackFile.fileSize()); + pTrackLocationInsert->bindValue(":location", fileInfo.location()); + pTrackLocationInsert->bindValue(":directory", fileInfo.locationPath()); + pTrackLocationInsert->bindValue(":filename", fileInfo.fileName()); + pTrackLocationInsert->bindValue(":filesize", fileInfo.sizeInBytes()); pTrackLocationInsert->bindValue(":fs_deleted", 0); pTrackLocationInsert->bindValue(":needs_verification", 0); if (pTrackLocationInsert->exec()) { return true; } else { LOG_FAILED_QUERY(*pTrackLocationInsert) - << "Skip inserting duplicate track location" << trackFile.location(); + << "Skip inserting duplicate track location" << fileInfo.location(); return false; } } @@ -608,13 +608,13 @@ bool insertTrackLibrary( const mixxx::TrackRecord& trackRecord, const mixxx::BeatsPointer& pBeats, DbId trackLocationId, - const TrackFile& trackFile, + const mixxx::FileInfo& fileInfo, const QDateTime& trackDateAdded) { bindTrackLibraryValues(pTrackLibraryInsert, trackRecord, pBeats); if (!trackRecord.getDateAdded().isNull()) { qDebug() << "insertTrackLibrary: Track" - << trackFile + << fileInfo << "was added" << trackRecord.getDateAdded(); } @@ -636,7 +636,7 @@ bool insertTrackLibrary( // but marked deleted? Skip this track. LOG_FAILED_QUERY(*pTrackLibraryInsert) << "Failed to insert new track into library:" - << trackFile; + << fileInfo; return false; } return true; @@ -646,29 +646,29 @@ bool insertTrackLibrary( TrackId TrackDAO::addTracksAddTrack(const TrackPointer& pTrack, bool unremove) { DEBUG_ASSERT(pTrack); - const auto trackFile = pTrack->getFileInfo(); + const auto fileAccess = pTrack->getFileAccess(); VERIFY_OR_DEBUG_ASSERT(m_pQueryLibraryInsert || m_pQueryTrackLocationInsert || m_pQueryLibrarySelect || m_pQueryTrackLocationSelect) { qDebug() << "TrackDAO::addTracksAddTrack: needed SqlQuerys have not " "been prepared. Skipping track" - << trackFile; + << fileAccess.info(); return TrackId(); } qDebug() << "TrackDAO: Adding track" - << trackFile; + << fileAccess.info(); TrackId trackId; // Insert the track location into the corresponding table. This will fail // silently if the location is already in the table because it has a UNIQUE // constraint. - if (!insertTrackLocation(m_pQueryTrackLocationInsert.get(), trackFile)) { + if (!insertTrackLocation(m_pQueryTrackLocationInsert.get(), fileAccess.info())) { DEBUG_ASSERT(pTrack->getDateAdded().isValid()); // Inserting into track_locations failed, so the file already // exists. Query for its trackLocationId. - m_pQueryTrackLocationSelect->bindValue(":location", trackFile.location()); + m_pQueryTrackLocationSelect->bindValue(":location", fileAccess.info().location()); if (!m_pQueryTrackLocationSelect->exec()) { // We can't even select this, something is wrong. LOG_FAILED_QUERY(*m_pQueryTrackLocationSelect) @@ -691,7 +691,7 @@ TrackId TrackDAO::addTracksAddTrack(const TrackPointer& pTrack, bool unremove) { if (!m_pQueryLibrarySelect->exec()) { LOG_FAILED_QUERY(*m_pQueryLibrarySelect) << "Failed to query existing track: " - << trackFile; + << fileAccess.info(); return TrackId(); } if (m_queryLibraryIdColumn == UndefinedRecordIndex) { @@ -718,7 +718,7 @@ TrackId TrackDAO::addTracksAddTrack(const TrackPointer& pTrack, bool unremove) { if (!m_pQueryLibraryUpdate->exec()) { LOG_FAILED_QUERY(*m_pQueryLibraryUpdate) << "Failed to unremove existing track: " - << trackFile; + << fileAccess.info(); return TrackId(); } } @@ -756,7 +756,7 @@ TrackId TrackDAO::addTracksAddTrack(const TrackPointer& pTrack, bool unremove) { trackRecord, pBeats, trackLocationId, - trackFile, + fileAccess.info(), trackDateAdded)) { return TrackId(); } @@ -782,24 +782,26 @@ TrackId TrackDAO::addTracksAddTrack(const TrackPointer& pTrack, bool unremove) { return trackId; } -TrackPointer TrackDAO::addTracksAddFile(const TrackFile& trackFile, bool unremove) { +TrackPointer TrackDAO::addTracksAddFile( + const mixxx::FileAccess& fileAccess, + bool unremove) { // Check that track is a supported extension. // TODO(uklotzde): The following check can be skipped if // the track is already in the library. A refactoring is // needed to detect this before calling addTracksAddTrack(). - if (!SoundSourceProxy::isFileSupported(trackFile)) { + if (!SoundSourceProxy::isFileSupported(fileAccess.info())) { qWarning() << "TrackDAO::addTracksAddFile:" - << "Unsupported file type" - << trackFile.location(); + << "Unsupported file type" + << fileAccess.info().location(); return TrackPointer(); } - GlobalTrackCacheResolver cacheResolver(trackFile); + GlobalTrackCacheResolver cacheResolver(fileAccess); TrackPointer pTrack = cacheResolver.getTrack(); if (!pTrack) { qWarning() << "TrackDAO::addTracksAddFile:" - << "File not found" - << trackFile.location(); + << "File not found" + << fileAccess.info().location(); return TrackPointer(); } const TrackId oldTrackId = pTrack->getId(); @@ -812,15 +814,15 @@ TrackPointer TrackDAO::addTracksAddFile(const TrackFile& trackFile, bool unremov // TODO: These duplicates are only detected by chance when // the other track is currently cached. Instead file aliasing // must be detected reliably in any situation. - if (trackFile.location() != trackLocation) { + if (fileAccess.info().location() != trackLocation) { kLogger.warning() << "Cannot add track:" << "Both the new track at" - << trackFile.location() + << fileAccess.info().location() << "and an existing track at" << trackLocation << "are referencing the same file" - << trackFile.canonicalLocation(); + << fileAccess.info().canonicalLocation(); return TrackPointer(); } return pTrack; @@ -944,9 +946,9 @@ QList TrackDAO::getAllTrackRefs(const QDir& rootDir) const { const int idColumn = query.record().indexOf("id"); const int locationColumn = query.record().indexOf("location"); while (query.next()) { - auto trackId = TrackId(query.value(idColumn)); - auto trackFile = TrackFile(query.value(locationColumn).toString()); - trackRefs.append(TrackRef::fromFileInfo(trackFile, trackId)); + const auto trackId = TrackId(query.value(idColumn)); + const auto fileLocation = query.value(locationColumn).toString(); + trackRefs.append(TrackRef::fromFilePath(fileLocation, trackId)); } return trackRefs; @@ -1407,7 +1409,8 @@ TrackPointer TrackDAO::getTrackById(TrackId trackId) const { // Location is the first column. const QString trackLocation(queryRecord.value(0).toString()); - GlobalTrackCacheResolver cacheResolver(TrackFile(trackLocation), trackId); + GlobalTrackCacheResolver cacheResolver( + mixxx::FileAccess(mixxx::FileInfo(trackLocation)), trackId); pTrack = cacheResolver.getTrack(); if (cacheResolver.getLookupResult() == GlobalTrackCacheLookupResult::Hit) { // Due to race conditions the track might have been reloaded @@ -1858,11 +1861,11 @@ bool TrackDAO::detectMovedTracks( DEBUG_ASSERT(oldTrackId != newTrackId); DEBUG_ASSERT(oldTrackLocationId != newTrackLocationId); - auto missingTrackRef = TrackRef::fromFileInfo( - TrackFile(oldTrackLocation), + auto missingTrackRef = TrackRef::fromFilePath( + oldTrackLocation, std::move(oldTrackId)); - auto addedTrackRef = TrackRef::fromFileInfo( - TrackFile(newTrackLocation), + auto addedTrackRef = TrackRef::fromFilePath( + newTrackLocation, std::move(newTrackId)); auto relocatedTrack = RelocatedTrack( std::move(missingTrackRef), @@ -2085,21 +2088,18 @@ void TrackDAO::detectCoverArtForTracksWithoutCover(volatile const bool* pCancel, //qDebug() << "Searching for cover art for" << trackLocation; emit progressCoverArt(track.trackLocation); - TrackFile trackFile(track.trackLocation); - if (!trackFile.checkFileExists()) { - //qDebug() << trackLocation << "does not exist"; + const auto fileInfo = mixxx::FileInfo(track.trackLocation); + if (!fileInfo.checkFileExists()) { + //qDebug() << fileInfo << "does not exist"; continue; } - SecurityTokenPointer pToken = Sandbox::openSecurityToken( - trackFile.asFileInfo(), true); const auto embeddedCover = CoverArtUtils::extractEmbeddedCover( - trackFile, - pToken); + mixxx::FileAccess(fileInfo)); const auto coverInfo = coverInfoGuesser.guessCoverInfo( - trackFile, + fileInfo, track.trackAlbum, embeddedCover); DEBUG_ASSERT(coverInfo.source != CoverInfo::UNKNOWN); @@ -2169,15 +2169,15 @@ TrackPointer TrackDAO::getOrAddTrack( return pTrack; } -TrackFile TrackDAO::relocateCachedTrack( +mixxx::FileAccess TrackDAO::relocateCachedTrack( TrackId trackId, - TrackFile fileInfo) { + mixxx::FileAccess fileAccess) { QString trackLocation = getTrackLocation(trackId); if (trackLocation.isEmpty()) { // not found - return fileInfo; + return fileAccess; } else { - return TrackFile(trackLocation); + return mixxx::FileAccess(mixxx::FileInfo(trackLocation)); } } diff --git a/src/library/dao/trackdao.h b/src/library/dao/trackdao.h index e707fb3ddca..409f1582b7f 100644 --- a/src/library/dao/trackdao.h +++ b/src/library/dao/trackdao.h @@ -51,7 +51,7 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC void finish(); QList resolveTrackIds( - const QList &trackFiles, + const QList& fileInfos, ResolveTrackIdFlags flags = ResolveTrackIdFlag::ResolveOnly); TrackId getTrackIdByRef( @@ -125,8 +125,15 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC const TrackPointer& pTrack, bool unremove); TrackPointer addTracksAddFile( - const TrackFile& trackFile, + const mixxx::FileAccess& fileAccess, bool unremove); + TrackPointer addTracksAddFile( + const QString& filePath, + bool unremove) { + return addTracksAddFile( + mixxx::FileAccess(mixxx::FileInfo(filePath)), + unremove); + } void addTracksFinish(bool rollback = false); bool updateTrack(Track* pTrack) const; @@ -162,9 +169,9 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC QSet* pTracksChanged); // Callback for GlobalTrackCache - TrackFile relocateCachedTrack( + mixxx::FileAccess relocateCachedTrack( TrackId trackId, - TrackFile fileInfo) override; + mixxx::FileAccess fileAccess) override; CueDAO& m_cueDao; PlaylistDAO& m_playlistDao; diff --git a/src/library/dlgtrackinfo.cpp b/src/library/dlgtrackinfo.cpp index ecec9050988..7a5a8f6a93f 100644 --- a/src/library/dlgtrackinfo.cpp +++ b/src/library/dlgtrackinfo.cpp @@ -607,8 +607,7 @@ void DlgTrackInfo::slotImportMetadataFromFile() { // modified and we want to read fresh metadata directly from the // file. Otherwise the changes in m_pLoadedTrack would be lost. TrackPointer pTrack = SoundSourceProxy::importTemporaryTrack( - m_pLoadedTrack->getFileInfo(), - m_pLoadedTrack->getSecurityToken()); + m_pLoadedTrack->getFileAccess()); DEBUG_ASSERT(pTrack); populateFields(*pTrack); } diff --git a/src/library/export/engineprimeexportjob.cpp b/src/library/export/engineprimeexportjob.cpp index 3701f93e7fe..a37b79f3839 100644 --- a/src/library/export/engineprimeexportjob.cpp +++ b/src/library/export/engineprimeexportjob.cpp @@ -93,12 +93,12 @@ QString exportFile(const QSharedPointer pRequest, // been modified (or the destination doesn't exist). To ensure no // chance of filename clashes, and to keep things simple, we will prefix // the destination files with the DB track identifier. - TrackFile srcFileInfo = pTrack->getFileInfo(); + mixxx::FileInfo srcFileInfo = pTrack->getFileInfo(); const auto trackId = pTrack->getId().value(); QString dstFilename = QString::number(trackId) + " - " + srcFileInfo.fileName(); QString dstPath = pRequest->musicFilesDir.filePath(dstFilename); if (!QFile::exists(dstPath) || - srcFileInfo.fileLastModified() > QFileInfo{dstPath}.lastModified()) { + srcFileInfo.lastModified() > QFileInfo{dstPath}.lastModified()) { const auto srcPath = srcFileInfo.location(); QFile::copy(srcPath, dstPath); } @@ -152,7 +152,7 @@ void exportMetadata(djinterop::database* pDatabase, snapshot.composer = pTrack->getComposer().toStdString(); snapshot.key = toDjinteropKey(pTrack->getKey()); int64_t lastModifiedMillisSinceEpoch = - pTrack->getFileInfo().fileLastModified().toMSecsSinceEpoch(); + pTrack->getFileInfo().lastModified().toMSecsSinceEpoch(); std::chrono::system_clock::time_point lastModifiedAt{ std::chrono::milliseconds{lastModifiedMillisSinceEpoch}}; snapshot.last_modified_at = lastModifiedAt; @@ -408,8 +408,7 @@ void EnginePrimeExportJob::loadIds(const QSet& crateIds) { const auto location = m_pTrackCollectionManager->internalCollection() ->getTrackDAO() .getTrackLocation(trackId); - const auto trackFile = TrackFile(location); - m_trackRefs.append(TrackRef::fromFileInfo(trackFile, trackId)); + m_trackRefs.append(TrackRef::fromFilePath(location, trackId)); } } } diff --git a/src/library/export/trackexportworker.cpp b/src/library/export/trackexportworker.cpp index dd9a16f5677..0acd2730a14 100644 --- a/src/library/export/trackexportworker.cpp +++ b/src/library/export/trackexportworker.cpp @@ -9,7 +9,7 @@ namespace { -QString rewriteFilename(const QFileInfo& fileinfo, int index) { +QString rewriteFilename(const mixxx::FileInfo& fileinfo, int index) { // We don't have total control over the inputs, so definitely // don't use .arg().arg().arg(). const QString index_str = QString("%1").arg(index, 4, 10, QChar('0')); @@ -19,37 +19,34 @@ QString rewriteFilename(const QFileInfo& fileinfo, int index) { // Iterate over a list of tracks and generate a minimal set of files to copy. // Finds duplicate filenames. Munges filenames if they refer to different files, // and skips if they refer to the same disk location. Returns a map from -// QString (the destination possibly-munged filenames) to QFileinfo (the source +// QString (the destination possibly-munged filenames) to QFileInfo (the source // file information). -QMap createCopylist(const TrackPointerList& tracks) { +QMap createCopylist(const TrackPointerList& tracks) { // QMap is a non-obvious return value, but it's easy for callers to use // in practice and is the best object for producing the final list // efficiently. - QMap copylist; - for (const auto& it : tracks) { - if (it->getCanonicalLocation().isEmpty()) { + QMap copylist; + for (const auto& pTrack : tracks) { + auto fileInfo = pTrack->getFileInfo(); + if (fileInfo.resolveCanonicalLocation().isEmpty()) { qWarning() << "File not found or inaccessible while exporting" - << it->getFileInfo(); + << fileInfo; // Skip file continue; } - // When obtaining the canonical location the file info of the - // track might have been refreshed. Get it now. - const auto trackFile = it->getFileInfo(); - - const auto fileName = trackFile.fileName(); + const auto fileName = fileInfo.fileName(); auto destFileName = fileName; int duplicateCounter = 0; do { const auto duplicateIter = copylist.find(destFileName); if (duplicateIter == copylist.end()) { // Usual case -- haven't seen this filename before, so add it. - copylist[destFileName] = trackFile; + copylist[destFileName] = fileInfo; break; } - if (trackFile.canonicalLocation() == duplicateIter->canonicalLocation()) { + if (fileInfo.canonicalLocation() == duplicateIter->canonicalLocation()) { // Silently ignore and skip duplicate files that point // to the same location on disk break; @@ -59,11 +56,11 @@ QMap createCopylist(const TrackPointerList& tracks) { << "Failed to generate a unique file name from" << fileName << "while exporting" - << trackFile.location(); + << fileInfo.location(); break; } // Next round - destFileName = rewriteFilename(trackFile.asFileInfo(), duplicateCounter); + destFileName = rewriteFilename(fileInfo, duplicateCounter); } while (!destFileName.isEmpty()); } return copylist; @@ -73,14 +70,14 @@ QMap createCopylist(const TrackPointerList& tracks) { void TrackExportWorker::run() { int i = 0; - QMap copy_list = createCopylist(m_tracks); + QMap copy_list = createCopylist(m_tracks); for (auto it = copy_list.constBegin(); it != copy_list.constEnd(); ++it) { // We emit progress twice per loop, which may seem excessive, but it // guarantees that we emit a sane progress before we start and after // we end. In between, each filename will get its own visible tick // on the bar, which looks really nice. emit progress(it->fileName(), i, copy_list.size()); - copyFile((*it).asFileInfo(), it.key()); + copyFile(*it, it.key()); if (atomicLoadAcquire(m_bStop)) { emit canceled(); return; @@ -90,9 +87,10 @@ void TrackExportWorker::run() { } } -void TrackExportWorker::copyFile(const QFileInfo& source_fileinfo, - const QString& dest_filename) { - QString sourceFilename = source_fileinfo.canonicalFilePath(); +void TrackExportWorker::copyFile( + const mixxx::FileInfo& source_fileinfo, + const QString& dest_filename) { + QString sourceFilename = source_fileinfo.canonicalLocation(); const QString dest_path = QDir(m_destDir).filePath(dest_filename); QFileInfo dest_fileinfo(dest_path); diff --git a/src/library/export/trackexportworker.h b/src/library/export/trackexportworker.h index 353abc002ff..0dd7d660550 100644 --- a/src/library/export/trackexportworker.h +++ b/src/library/export/trackexportworker.h @@ -7,8 +7,7 @@ #include #include "track/track_decl.h" - -class QFileInfo; +#include "util/fileinfo.h" // A QThread class for copying a list of files to a single destination directory. // Currently does not preserve subdirectory relationships. This class performs @@ -71,8 +70,8 @@ class TrackExportWorker : public QThread { // exists, will emit an overwrite request signal to ask how to proceed. // On unrecoverable error, sets the error message and stops the export // process entirely. - void copyFile(const QFileInfo& source_fileinfo, - const QString& dest_filename); + void copyFile(const mixxx::FileInfo& source_fileinfo, + const QString& dest_filename); // Emit a signal requesting overwrite mode, and block until we get an // answer. Updates m_overwriteMode appropriately. diff --git a/src/library/itunes/itunesfeature.cpp b/src/library/itunes/itunesfeature.cpp index e65a0373889..b6b75730ee1 100644 --- a/src/library/itunes/itunesfeature.cpp +++ b/src/library/itunes/itunesfeature.cpp @@ -184,19 +184,19 @@ void ITunesFeature::activate(bool forceReload) { m_dbfile = getiTunesMusicPath(); } - QFileInfo dbFile(m_dbfile); - if (!m_dbfile.isEmpty() && dbFile.exists()) { + mixxx::FileInfo fileInfo(m_dbfile); + if (fileInfo.checkFileExists()) { // Users of Mixxx <1.12.0 didn't support sandboxing. If we are sandboxed // and using a custom iTunes path then we have to ask for access to this // file. - Sandbox::askForAccess(m_dbfile); + Sandbox::askForAccess(&fileInfo); } else { // if the path we got between the default and the database doesn't // exist, ask for a new one and use/save it if it exists m_dbfile = QFileDialog::getOpenFileName( nullptr, tr("Select your iTunes library"), QDir::homePath(), "*.xml"); - QFileInfo dbFile(m_dbfile); - if (m_dbfile.isEmpty() || !dbFile.exists()) { + mixxx::FileInfo fileInfo(m_dbfile); + if (!fileInfo.checkFileExists()) { return; } @@ -205,7 +205,7 @@ void ITunesFeature::activate(bool forceReload) { // this folder. Create a security bookmark while we have permission so // that we can access the folder on future runs. We need to canonicalize // the path so we first wrap the directory string with a QDir. - Sandbox::createSecurityToken(dbFile); + Sandbox::createSecurityToken(&fileInfo); settings.setValue(ITDB_PATH_KEY, m_dbfile); } m_isActivated = true; @@ -251,8 +251,8 @@ void ITunesFeature::onRightClick(const QPoint& globalPos) { QString dbfile = QFileDialog::getOpenFileName( nullptr, tr("Select your iTunes library"), QDir::homePath(), "*.xml"); - QFileInfo dbFileInfo(dbfile); - if (dbfile.isEmpty() || !dbFileInfo.exists()) { + mixxx::FileInfo dbFileInfo(dbfile); + if (!dbFileInfo.checkFileExists()) { return; } // The user has picked a new directory via a file dialog. This means the @@ -260,7 +260,7 @@ void ITunesFeature::onRightClick(const QPoint& globalPos) { // this folder. Create a security bookmark while we have permission so // that we can access the folder on future runs. We need to canonicalize // the path so we first wrap the directory string with a QDir. - Sandbox::createSecurityToken(dbFileInfo); + Sandbox::createSecurityToken(&dbFileInfo); settings.setValue(ITDB_PATH_KEY, dbfile); activate(true); // clears tables before parsing @@ -582,7 +582,7 @@ void ITunesFeature::parseTrack(QXmlStreamReader& xml, QSqlQuery& query) { continue; } if (key == kLocation) { - location = TrackFile::fromUrl(QUrl(content)).location(); + location = mixxx::FileInfo::fromQUrl(QUrl(content)).location(); // Replace first part of location with the mixxx iTunes Root // on systems where iTunes installed it only strips //localhost // on iTunes from foreign systems the mount point is replaced diff --git a/src/library/library.cpp b/src/library/library.cpp index 89036c59c77..f588f4d70ca 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -218,14 +218,14 @@ Library::Library( // 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. const auto rootDirs = m_pTrackCollectionManager->internalCollection()->loadRootDirs(); - for (const mixxx::FileInfo& dirInfo : rootDirs) { + for (mixxx::FileInfo dirInfo : rootDirs) { if (!dirInfo.exists() || !dirInfo.isDir()) { kLogger.warning() << "Skipping access check for missing or invalid directory" << dirInfo; continue; } - if (Sandbox::askForAccess(dirInfo.canonicalLocation())) { + if (Sandbox::askForAccess(&dirInfo)) { kLogger.info() << "Access to directory" << dirInfo @@ -461,7 +461,7 @@ void Library::slotLoadTrack(TrackPointer pTrack) { } void Library::slotLoadLocationToPlayer(const QString& location, const QString& group) { - auto trackRef = TrackRef::fromFileInfo(location); + auto trackRef = TrackRef::fromFilePath(location); TrackPointer pTrack = m_pTrackCollectionManager->getOrAddTrack(trackRef); if (pTrack) { emit loadTrackToPlayer(pTrack, group); diff --git a/src/library/parser.cpp b/src/library/parser.cpp index eca875f3682..8b4cd937b33 100644 --- a/src/library/parser.cpp +++ b/src/library/parser.cpp @@ -142,19 +142,19 @@ bool Parser::isUtf8(const char* string) { return true; } -TrackFile Parser::playlistEntryToTrackFile( +mixxx::FileInfo Parser::playlistEntryToFileInfo( const QString& playlistEntry, const QString& basePath) { if (playlistEntry.startsWith("file:")) { // URLs are always absolute - return TrackFile::fromUrl(QUrl(playlistEntry)); + return mixxx::FileInfo::fromQUrl(QUrl(playlistEntry)); } auto filePath = QString(playlistEntry).replace('\\', '/'); - auto trackFile = TrackFile(filePath); - if (basePath.isEmpty() || trackFile.asFileInfo().isAbsolute()) { + auto trackFile = mixxx::FileInfo(filePath); + if (basePath.isEmpty() || trackFile.isAbsolute()) { return trackFile; } else { // Fallback: Relative to base path - return TrackFile(QDir(basePath), filePath); + return mixxx::FileInfo(QDir(basePath), filePath); } } diff --git a/src/library/parser.h b/src/library/parser.h index 4a666bdde81..00d43ff35c3 100644 --- a/src/library/parser.h +++ b/src/library/parser.h @@ -20,11 +20,11 @@ from it and overwrite the parse function and add class specific functions to it afterwards for proper functioning **/ +#include #include #include -#include -#include "track/trackfile.h" +#include "util/fileinfo.h" class Parser : public QObject { public: @@ -55,7 +55,7 @@ class Parser : public QObject { // check for Utf8 encoding static bool isUtf8(const char* string); // Resolve an absolute or relative file path - TrackFile playlistEntryToTrackFile( + mixxx::FileInfo playlistEntryToFileInfo( const QString& playlistEntry, const QString& basePath = QString()); }; diff --git a/src/library/parserm3u.cpp b/src/library/parserm3u.cpp index fac4540a00a..3ad5bcc34c9 100644 --- a/src/library/parserm3u.cpp +++ b/src/library/parserm3u.cpp @@ -97,7 +97,7 @@ QString ParserM3u::getFilePath(QTextStream* stream, const QString& basePath) { // Skip comments continue; } - auto trackFile = playlistEntryToTrackFile(textline, basePath); + auto trackFile = playlistEntryToFileInfo(textline, basePath); if (trackFile.checkFileExists()) { return trackFile.location(); } diff --git a/src/library/parserpls.cpp b/src/library/parserpls.cpp index 5e26bd2b50c..be2097b4444 100644 --- a/src/library/parserpls.cpp +++ b/src/library/parserpls.cpp @@ -114,7 +114,7 @@ QString ParserPls::getFilePath(QTextStream *stream, const QString& basePath) { ++iPos; QString filename = textline.right(textline.length() - iPos); - auto trackFile = playlistEntryToTrackFile(filename, basePath); + auto trackFile = playlistEntryToFileInfo(filename, basePath); if (trackFile.checkFileExists()) { return trackFile.location(); } diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index c0dc9f8668d..227c5d2bd0a 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -483,7 +483,8 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem* dev queryInsertIntoDevicePlaylistTracks.bindValue(":playlist_id", playlistID); - if (!Sandbox::askForAccess(dbPath)) { + mixxx::FileInfo fileInfo(dbPath); + if (!Sandbox::askForAccess(&fileInfo)) { return QString(); } std::ifstream ifs(dbPath.toStdString(), std::ifstream::binary); diff --git a/src/library/rhythmbox/rhythmboxfeature.cpp b/src/library/rhythmbox/rhythmboxfeature.cpp index 53df4526d35..6e4f8e5cb8e 100644 --- a/src/library/rhythmbox/rhythmboxfeature.cpp +++ b/src/library/rhythmbox/rhythmboxfeature.cpp @@ -150,7 +150,8 @@ TreeItem* RhythmboxFeature::importMusicCollection() { } } - if (!Sandbox::askForAccess(QFileInfo(db).absoluteFilePath()) || + mixxx::FileInfo fileInfo(db); + if (!Sandbox::askForAccess(&fileInfo) || !db.open(QIODevice::ReadOnly)) { return nullptr; } @@ -331,8 +332,8 @@ void RhythmboxFeature::importTrack(QXmlStreamReader &xml, QSqlQuery &query) { } } - const auto trackFile = TrackFile::fromUrl(locationUrl); - QString location = trackFile.location(); + const auto fileInfo = mixxx::FileInfo::fromQUrl(locationUrl); + QString location = fileInfo.location(); if (location.isEmpty()) { // here in case of smb:// location // TODO(XXX) QUrl does not support SMB:// locations does Mixxx? @@ -371,13 +372,13 @@ void RhythmboxFeature::importPlaylist(QXmlStreamReader &xml, //read next XML element xml.readNext(); if (xml.isStartElement() && xml.name() == "location") { - const auto trackFile = TrackFile::fromUrl(xml.readElementText()); + const auto fileInfo = mixxx::FileInfo::fromQUrl(xml.readElementText()); //get the ID of the file in the rhythmbox_library table int track_id = -1; QSqlQuery finder_query(m_database); finder_query.prepare("select id from rhythmbox_library where location=:path"); - finder_query.bindValue(":path", trackFile.location()); + finder_query.bindValue(":path", fileInfo.location()); bool success = finder_query.exec(); if (success) { diff --git a/src/library/scanner/importfilestask.cpp b/src/library/scanner/importfilestask.cpp index 7b3b6704409..62de43bc0ce 100644 --- a/src/library/scanner/importfilestask.cpp +++ b/src/library/scanner/importfilestask.cpp @@ -2,7 +2,6 @@ #include "library/scanner/libraryscanner.h" #include "moc_importfilestask.cpp" -#include "track/trackfile.h" #include "util/timer.h" ImportFilesTask::ImportFilesTask(LibraryScanner* pScanner, @@ -31,7 +30,7 @@ void ImportFilesTask::run() { return; } - const QString trackLocation(TrackFile(fileInfo).location()); + const QString trackLocation(mixxx::FileInfo(fileInfo).location()); //qDebug() << "ImportFilesTask::run" << trackLocation; // If the file does not exist in the database then add it. If it diff --git a/src/library/scanner/libraryscanner.cpp b/src/library/scanner/libraryscanner.cpp index 62a117da248..f207ecd6f99 100644 --- a/src/library/scanner/libraryscanner.cpp +++ b/src/library/scanner/libraryscanner.cpp @@ -12,7 +12,6 @@ #include "util/db/dbconnectionpooled.h" #include "util/db/dbconnectionpooler.h" #include "util/db/fwdsqlquery.h" -#include "util/fileaccess.h" #include "util/logger.h" #include "util/performancetimer.h" #include "util/timer.h" @@ -539,7 +538,9 @@ void LibraryScanner::slotAddNewTrack(const QString& trackPath) { //kLogger.debug() << "slotAddNewTrack" << trackPath; ScopedTimer timer("LibraryScanner::addNewTrack"); // For statistics tracking and to detect moved tracks - TrackPointer pTrack(m_trackDao.addTracksAddFile(trackPath, false)); + TrackPointer pTrack = m_trackDao.addTracksAddFile( + trackPath, + false); if (pTrack) { DEBUG_ASSERT(!pTrack->isDirty()); // The track's actual location might differ from the diff --git a/src/library/serato/seratofeature.cpp b/src/library/serato/seratofeature.cpp index 48fbab3d68d..f0d6a6fec31 100644 --- a/src/library/serato/seratofeature.cpp +++ b/src/library/serato/seratofeature.cpp @@ -347,8 +347,9 @@ QString parseCrate( return QString(); } + mixxx::FileInfo fileInfo(crateFilePath); QFile crateFile(crateFilePath); - if (!Sandbox::askForAccess(crateFilePath) || !crateFile.open(QIODevice::ReadOnly)) { + if (!Sandbox::askForAccess(&fileInfo) || !crateFile.open(QIODevice::ReadOnly)) { qWarning() << "Failed to open file " << crateFilePath << " for reading."; @@ -528,8 +529,9 @@ QString parseDatabase(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem* dat ":serato_db" ")"); + mixxx::FileInfo fileInfo(databaseFilePath); QFile databaseFile(databaseFilePath); - if (!Sandbox::askForAccess(databaseFilePath) || !databaseFile.open(QIODevice::ReadOnly)) { + if (!Sandbox::askForAccess(&fileInfo) || !databaseFile.open(QIODevice::ReadOnly)) { qWarning() << "Failed to open file " << databaseFilePath << " for reading."; diff --git a/src/library/trackcollection.cpp b/src/library/trackcollection.cpp index 69286ff2fc8..875a094e27a 100644 --- a/src/library/trackcollection.cpp +++ b/src/library/trackcollection.cpp @@ -211,9 +211,11 @@ void TrackCollection::relocateDirectory(const QString& oldDir, const QString& ne } QList TrackCollection::resolveTrackIds( - const QList& trackFiles, + const QList& trackFiles, TrackDAO::ResolveTrackIdFlags flags) { - QList trackIds = m_trackDao.resolveTrackIds(trackFiles, flags); + QList trackIds = m_trackDao.resolveTrackIds( + trackFiles, + flags); if (flags & TrackDAO::ResolveTrackIdFlag::UnhideHidden) { unhideTracks(trackIds); } @@ -221,8 +223,9 @@ QList TrackCollection::resolveTrackIds( } QList TrackCollection::resolveTrackIdsFromUrls( - const QList& urls, bool addMissing) { - QList files = DragAndDropHelper::supportedTracksFromUrls(urls, false, true); + const QList& urls, + bool addMissing) { + QList files = DragAndDropHelper::supportedTracksFromUrls(urls, false, true); if (files.isEmpty()) { return QList(); } @@ -237,14 +240,14 @@ QList TrackCollection::resolveTrackIdsFromUrls( QList TrackCollection::resolveTrackIdsFromLocations( const QList& locations) { - QList trackFiles; - trackFiles.reserve(locations.size()); + QList fileInfos; + fileInfos.reserve(locations.size()); for (const QString& location : locations) { - trackFiles.append(TrackFile(location)); + fileInfos.append(mixxx::FileInfo(location)); } - return resolveTrackIds(trackFiles, - TrackDAO::ResolveTrackIdFlag::UnhideHidden - | TrackDAO::ResolveTrackIdFlag::AddMissing); + return resolveTrackIds( + fileInfos, + TrackDAO::ResolveTrackIdFlag::UnhideHidden | TrackDAO::ResolveTrackIdFlag::AddMissing); } bool TrackCollection::hideTracks(const QList& trackIds) { diff --git a/src/library/trackcollection.h b/src/library/trackcollection.h index 5155f417fc5..cd87661f20f 100644 --- a/src/library/trackcollection.h +++ b/src/library/trackcollection.h @@ -79,7 +79,7 @@ class TrackCollection : public QObject, // This function returns a track ID of all file in the list not already visible, // it adds and unhides the tracks as well. QList resolveTrackIds( - const QList &trackFiles, + const QList& trackFiles, TrackDAO::ResolveTrackIdFlags flags); QList resolveTrackIdsFromUrls( const QList& urls, diff --git a/src/library/trackcollectionmanager.cpp b/src/library/trackcollectionmanager.cpp index 6bc9b102948..c0cb9d8f9c0 100644 --- a/src/library/trackcollectionmanager.cpp +++ b/src/library/trackcollectionmanager.cpp @@ -198,15 +198,28 @@ void TrackCollectionManager::saveTrack( DEBUG_ASSERT(pTrack); DEBUG_ASSERT(pTrack->getDateAdded().isValid()); - // The metadata must be exported while the cache is locked to - // ensure that we have exclusive (write) access on the file - // and not reader or writer is accessing the same file - // concurrently. - exportTrackMetadata(pTrack, mode); - // The dirty flag is reset while saving the track in the internal // collection! const bool trackDirty = pTrack->isDirty(); + if (!trackDirty) { + return; + } + + const auto fileInfo = pTrack->getFileInfo(); + if (fileInfo.checkFileExists()) { + // The metadata must be exported while the cache is locked to + // ensure that we have exclusive (write) access on the file + // and not reader or writer is accessing the same file + // concurrently. + exportTrackMetadata(pTrack, mode); + } else { + // Missing tracks should not be modified until they either + // reappear or are purged. + kLogger.debug() + << "Skip saving of missing track" + << fileInfo.location(); + return; + } // This operation must be executed synchronously while the cache is // locked to prevent that a new track is created from outdated @@ -464,7 +477,7 @@ void TrackCollectionManager::afterTracksUpdated(const QSet& updatedTrac for (const auto& trackId : updatedTrackIds) { auto trackLocation = m_pInternalCollection->getTrackDAO().getTrackLocation(trackId); if (!trackLocation.isEmpty()) { - trackRefs.append(TrackRef::fromFileInfo(trackLocation, trackId)); + trackRefs.append(TrackRef::fromFilePath(trackLocation, trackId)); } } DEBUG_ASSERT(trackRefs.size() <= updatedTrackIds.size()); diff --git a/src/library/trackset/crate/cratetablemodel.cpp b/src/library/trackset/crate/cratetablemodel.cpp index 34586a133de..2a590a6dc66 100644 --- a/src/library/trackset/crate/cratetablemodel.cpp +++ b/src/library/trackset/crate/cratetablemodel.cpp @@ -64,7 +64,7 @@ bool CrateTableModel::addTrack(const QModelIndex& index, const QString& location Q_UNUSED(index); // This will only succeed if the file actually exist. - TrackFile fileInfo(location); + mixxx::FileInfo fileInfo(location); if (!fileInfo.checkFileExists()) { qDebug() << "CrateTableModel::addTrack:" << "File" << location << "not found"; diff --git a/src/library/traktor/traktorfeature.cpp b/src/library/traktor/traktorfeature.cpp index 35f77ab0039..2cfa0aedce1 100644 --- a/src/library/traktor/traktorfeature.cpp +++ b/src/library/traktor/traktorfeature.cpp @@ -205,8 +205,9 @@ TreeItem* TraktorFeature::importLibrary(const QString& file) { ":rating,:key)"); //Parse Trakor XML file using SAX (for performance) + mixxx::FileInfo fileInfo(file); QFile traktor_file(file); - if (!Sandbox::askForAccess(file) || !traktor_file.open(QIODevice::ReadOnly)) { + if (!Sandbox::askForAccess(&fileInfo) || !traktor_file.open(QIODevice::ReadOnly)) { qDebug() << "Cannot open Traktor music collection"; return nullptr; } @@ -569,7 +570,8 @@ QString TraktorFeature::getTraktorMusicDatabase() { // We may not have access to this directory since it is in the user's // Documents folder. Ask for access if we don't have it. if (ni_directory.exists()) { - Sandbox::askForAccess(ni_directory.canonicalPath()); + auto fileInfo = mixxx::FileInfo(ni_directory); + Sandbox::askForAccess(&fileInfo); } //Iterate over the subfolders diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp index 350bcf5911d..d377c86d5bf 100644 --- a/src/mixer/basetrackplayer.cpp +++ b/src/mixer/basetrackplayer.cpp @@ -376,12 +376,15 @@ void BaseTrackPlayerImpl::disconnectLoadedTrack() { } void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer pNewTrack, bool bPlay) { - qDebug() << "BaseTrackPlayerImpl::slotLoadTrack" << getGroup(); + //qDebug() << "BaseTrackPlayerImpl::slotLoadTrack" << getGroup(); // Before loading the track, ensure we have access. This uses lazy // evaluation to make sure track isn't NULL before we dereference it. - if (pNewTrack && !Sandbox::askForAccess(pNewTrack->getCanonicalLocation())) { - // We don't have access. - return; + if (pNewTrack) { + auto fileInfo = pNewTrack->getFileInfo(); + if (!Sandbox::askForAccess(&fileInfo)) { + // We don't have access. + return; + } } auto pOldTrack = unloadTrack(); diff --git a/src/mixxx.cpp b/src/mixxx.cpp index 3ac51af969a..11478d9f45a 100644 --- a/src/mixxx.cpp +++ b/src/mixxx.cpp @@ -799,8 +799,8 @@ void MixxxMainWindow::slotFileLoadSongPlayer(int deck) { // folder. Create a security bookmark while we have permission so that // we can access the folder on future runs. We need to canonicalize the // path so we first wrap the directory string with a QDir. - QFileInfo trackInfo(trackPath); - Sandbox::createSecurityToken(trackInfo); + mixxx::FileInfo fileInfo(trackPath); + Sandbox::createSecurityToken(&fileInfo); m_pCoreServices->getPlayerManager()->slotLoadToDeck(trackPath, deck); } diff --git a/src/sources/soundsourceproxy.cpp b/src/sources/soundsourceproxy.cpp index cc07e1ebe7f..a1282a9a969 100644 --- a/src/sources/soundsourceproxy.cpp +++ b/src/sources/soundsourceproxy.cpp @@ -251,19 +251,14 @@ bool SoundSourceProxy::registerProviders() { // static bool SoundSourceProxy::isUrlSupported(const QUrl& url) { - return isFileSupported(TrackFile::fromUrl(url)); + return isFileSupported(mixxx::FileInfo::fromQUrl(url)); } // static -bool SoundSourceProxy::isFileSupported(const QFileInfo& fileInfo) { +bool SoundSourceProxy::isFileSupported(const mixxx::FileInfo& fileInfo) { return isFileNameSupported(fileInfo.fileName()); } -// static -bool SoundSourceProxy::isFileSupported(const TrackFile& trackFile) { - return isFileNameSupported(trackFile.fileName()); -} - // static bool SoundSourceProxy::isFileNameSupported(const QString& fileName) { return fileName.contains(getSupportedFileNamesRegex()); @@ -309,11 +304,8 @@ SoundSourceProxy::allProviderRegistrationsForUrl( //static TrackPointer SoundSourceProxy::importTemporaryTrack( - TrackFile trackFile, - SecurityTokenPointer pSecurityToken) { - TrackPointer pTrack = Track::newTemporary( - std::move(trackFile), - std::move(pSecurityToken)); + mixxx::FileAccess trackFileAccess) { + TrackPointer pTrack = Track::newTemporary(std::move(trackFileAccess)); // Lock the track cache while populating the temporary track // object to ensure that no metadata is exported into any file // while reading from this file. Since locking individual files @@ -325,16 +317,13 @@ TrackPointer SoundSourceProxy::importTemporaryTrack( //static QImage SoundSourceProxy::importTemporaryCoverImage( - TrackFile trackFile, - SecurityTokenPointer pSecurityToken) { - if (!trackFile.checkFileExists()) { + mixxx::FileAccess trackFileAccess) { + if (!trackFileAccess.info().checkFileExists()) { // Silently ignore missing files to avoid spaming the log: // https://bugs.launchpad.net/mixxx/+bug/1875237 return QImage(); } - TrackPointer pTrack = Track::newTemporary( - std::move(trackFile), - std::move(pSecurityToken)); + TrackPointer pTrack = Track::newTemporary(std::move(trackFileAccess)); // Lock the track cache while populating the temporary track // object to ensure that no metadata is exported into any file // while reading from this file. Since locking individual files @@ -350,7 +339,7 @@ SoundSourceProxy::exportTrackMetadataBeforeSaving(Track* pTrack, UserSettingsPoi const auto fileInfo = pTrack->getFileInfo(); mixxx::MetadataSourcePointer pMetadataSource; { - auto proxy = SoundSourceProxy(fileInfo.toUrl()); + auto proxy = SoundSourceProxy(fileInfo.toQUrl()); // Ensure that the actual audio properties of the // stream are available before exporting metadata. // This might be needed for converting sample positions @@ -383,7 +372,7 @@ SoundSourceProxy::SoundSourceProxy( TrackPointer pTrack, const mixxx::SoundSourceProviderPointer& pProvider) : m_pTrack(std::move(pTrack)), - m_url(m_pTrack ? m_pTrack->getFileInfo().toUrl() : QUrl()), + m_url(m_pTrack ? m_pTrack->getFileInfo().toQUrl() : QUrl()), m_providerRegistrations(allProviderRegistrationsForUrl(m_url)), m_providerRegistrationIndex(-1) { initSoundSource(pProvider); @@ -643,20 +632,20 @@ void SoundSourceProxy::updateTrackFromSource( // SoundSourceProxy for just a few people. const bool splitArtistTitle = trackMetadata.getTrackInfo().getArtist().trimmed().isEmpty(); - const auto trackFile = m_pTrack->getFileInfo(); + const auto fileInfo = m_pTrack->getFileInfo(); kLogger.info() << "Parsing missing" << (splitArtistTitle ? "artist/title" : "title") << "from file name:" - << trackFile; + << fileInfo; if (trackMetadata.refTrackInfo().parseArtistTitleFromFileName( - trackFile.fileName(), splitArtistTitle)) { + fileInfo.fileName(), splitArtistTitle)) { // Pretend that metadata import succeeded metadataImported.first = mixxx::MetadataSource::ImportResult::Succeeded; if (metadataImported.second.isNull()) { // Since this is also some kind of metadata import, we mark the // track's metadata as synchronized with the time stamp of the file. - metadataImported.second = trackFile.fileLastModified(); + metadataImported.second = fileInfo.lastModified(); } } } diff --git a/src/sources/soundsourceproxy.h b/src/sources/soundsourceproxy.h index 5a149fe6e49..7e42d1ea72c 100644 --- a/src/sources/soundsourceproxy.h +++ b/src/sources/soundsourceproxy.h @@ -3,9 +3,14 @@ #include "preferences/usersettings.h" #include "sources/soundsourceproviderregistry.h" #include "track/track_decl.h" -#include "track/trackfile.h" #include "util/sandbox.h" +namespace mixxx { + +class FileAccess; + +} // namespace mixxx + /// Creates sound sources for tracks. Only intended to be used /// in a narrow scope and not shareable between multiple threads! class SoundSourceProxy { @@ -29,8 +34,7 @@ class SoundSourceProxy { } static bool isUrlSupported(const QUrl& url); - static bool isFileSupported(const TrackFile& trackFile); - static bool isFileSupported(const QFileInfo& fileInfo); + static bool isFileSupported(const mixxx::FileInfo& fileInfo); static bool isFileNameSupported(const QString& fileName); static bool isFileExtensionSupported(const QString& fileExtension); @@ -46,11 +50,9 @@ class SoundSourceProxy { // The following import functions ensure that the file will not be // written while reading it! static TrackPointer importTemporaryTrack( - TrackFile trackFile, - SecurityTokenPointer pSecurityToken = SecurityTokenPointer()); + mixxx::FileAccess trackFileAccess); static QImage importTemporaryCoverImage( - TrackFile trackFile, - SecurityTokenPointer pSecurityToken = SecurityTokenPointer()); + mixxx::FileAccess trackFileAccess); explicit SoundSourceProxy( TrackPointer pTrack, diff --git a/src/test/coverartcache_test.cpp b/src/test/coverartcache_test.cpp index 8cf21f799b8..22d49d1f46f 100644 --- a/src/test/coverartcache_test.cpp +++ b/src/test/coverartcache_test.cpp @@ -13,8 +13,7 @@ class CoverArtCacheTest : public LibraryTest, public CoverArtCache { protected: void loadCoverFromMetadata(const QString& trackLocation) { const QImage img = SoundSourceProxy::importTemporaryCoverImage( - trackLocation, - Sandbox::openSecurityToken(trackLocation, true)); + mixxx::FileAccess(mixxx::FileInfo(trackLocation))); ASSERT_FALSE(img.isNull()); CoverInfo info; diff --git a/src/test/coverartutils_test.cpp b/src/test/coverartutils_test.cpp index 8e80e62230a..c2a1dbd0db9 100644 --- a/src/test/coverartutils_test.cpp +++ b/src/test/coverartutils_test.cpp @@ -20,10 +20,10 @@ bool isSupportedFileExtension(const QString& fileExtension) { void extractEmbeddedCover( const QString& trackLocation, - const SecurityTokenPointer& pToken, const QImage& expectedImage) { const QImage actualImage( - CoverArtUtils::extractEmbeddedCover(trackLocation, pToken)); + CoverArtUtils::extractEmbeddedCover( + mixxx::FileAccess(mixxx::FileInfo(trackLocation)))); ASSERT_FALSE(actualImage.isNull()); EXPECT_EQ(expectedImage, actualImage); } @@ -40,58 +40,53 @@ TEST_F(CoverArtUtilTest, extractEmbeddedCover) { QImage referencePNGImage = QImage(kReferencePNGLocationTest); QImage referenceJPGImage = QImage(kReferenceJPGLocationTest); - // We never need to acquire security tokens for tests since we don't run - // them in a sandboxed environment. - - SecurityTokenPointer pToken; - if (isSupportedFileExtension("aiff")) { extractEmbeddedCover( - kTestDir.absoluteFilePath("cover-test.aiff"), pToken, referencePNGImage); + kTestDir.absoluteFilePath("cover-test.aiff"), referencePNGImage); } if (isSupportedFileExtension("flac")) { extractEmbeddedCover( - kTestDir.absoluteFilePath("cover-test.flac"), pToken, referencePNGImage); + kTestDir.absoluteFilePath("cover-test.flac"), referencePNGImage); } if (isSupportedFileExtension("m4a")) { extractEmbeddedCover( - kTestDir.absoluteFilePath("cover-test-itunes-12.3.0-aac.m4a"), pToken, referencePNGImage); + kTestDir.absoluteFilePath("cover-test-itunes-12.3.0-aac.m4a"), referencePNGImage); extractEmbeddedCover( - kTestDir.absoluteFilePath("cover-test-itunes-12.7.0-aac.m4a"), pToken, referencePNGImage); + kTestDir.absoluteFilePath("cover-test-itunes-12.7.0-aac.m4a"), referencePNGImage); extractEmbeddedCover( - kTestDir.absoluteFilePath("cover-test-itunes-12.7.0-alac.m4a"), pToken, referencePNGImage); + kTestDir.absoluteFilePath("cover-test-itunes-12.7.0-alac.m4a"), referencePNGImage); } if (isSupportedFileExtension("mp3")) { // PNG extractEmbeddedCover( - kTestDir.absoluteFilePath("cover-test-png.mp3"), pToken, referencePNGImage); + kTestDir.absoluteFilePath("cover-test-png.mp3"), referencePNGImage); // JPEG extractEmbeddedCover( - kTestDir.absoluteFilePath("cover-test-jpg.mp3"), pToken, referenceJPGImage); + kTestDir.absoluteFilePath("cover-test-jpg.mp3"), referenceJPGImage); } if (isSupportedFileExtension("ogg")) { extractEmbeddedCover( - kTestDir.absoluteFilePath("cover-test.ogg"), pToken, referencePNGImage); + kTestDir.absoluteFilePath("cover-test.ogg"), referencePNGImage); } if (isSupportedFileExtension("opus")) { // opus extractEmbeddedCover( - kTestDir.absoluteFilePath("cover-test.opus"), pToken, referencePNGImage); + kTestDir.absoluteFilePath("cover-test.opus"), referencePNGImage); } if (isSupportedFileExtension("wav")) { extractEmbeddedCover( - kTestDir.absoluteFilePath("cover-test.wav"), pToken, referencePNGImage); + kTestDir.absoluteFilePath("cover-test.wav"), referencePNGImage); } if (isSupportedFileExtension("wv")) { extractEmbeddedCover( - kTestDir.absoluteFilePath("cover-test.wv"), pToken, referencePNGImage); + kTestDir.absoluteFilePath("cover-test.wv"), referencePNGImage); } } @@ -161,7 +156,7 @@ TEST_F(CoverArtUtilTest, searchImage) { expected2.setImage(QImage(cLoc_foo)); covers << QFileInfo(cLoc_foo); CoverInfoRelative res2 = CoverArtUtils::selectCoverArtForTrack( - trackBaseName, trackAlbum, covers); + mixxx::FileInfo(trackBaseName), trackAlbum, covers); EXPECT_EQ(expected2, res2); QFile::remove(cLoc_foo); @@ -232,8 +227,8 @@ TEST_F(CoverArtUtilTest, searchImage) { expected2.coverLocation = cover.fileName(); expected2.setImage(QImage(cover.filePath())); } - res2 = CoverArtUtils::selectCoverArtForTrack(trackBaseName, trackAlbum, - prefCovers); + res2 = CoverArtUtils::selectCoverArtForTrack( + mixxx::FileInfo(trackBaseName), trackAlbum, prefCovers); EXPECT_QSTRING_EQ(expected2.coverLocation, res2.coverLocation); EXPECT_EQ(expected2.cacheKey(), res2.cacheKey()); EXPECT_EQ(expected2, res2); @@ -256,8 +251,8 @@ TEST_F(CoverArtUtilTest, searchImage) { prefCovers.append(QFileInfo(cLoc_coverjpg)); extraCovers << cLoc_coverJPG << cLoc_coverjpg; - res2 = CoverArtUtils::selectCoverArtForTrack(trackBaseName, trackAlbum, - prefCovers); + res2 = CoverArtUtils::selectCoverArtForTrack( + mixxx::FileInfo(trackBaseName), trackAlbum, prefCovers); expected2.setImage(QImage(cLoc_coverJPG)); expected2.coverLocation = "cover.JPG"; EXPECT_EQ(expected2, res2); @@ -273,8 +268,8 @@ TEST_F(CoverArtUtilTest, searchImage) { cLoc_albumName = QString(trackdir % "/" % trackAlbum % "." % qFormat); EXPECT_TRUE(img.save(cLoc_albumName, format)); prefCovers.append(QFileInfo(cLoc_albumName)); - res2 = CoverArtUtils::selectCoverArtForTrack(trackBaseName, trackAlbum, - prefCovers); + res2 = CoverArtUtils::selectCoverArtForTrack( + mixxx::FileInfo(trackBaseName), trackAlbum, prefCovers); expected2.setImage(QImage(cLoc_albumName)); expected2.coverLocation = trackAlbum % ".jpg"; EXPECT_EQ(expected2, res2); @@ -283,8 +278,8 @@ TEST_F(CoverArtUtilTest, searchImage) { cLoc_filename = QString(trackdir % "/" % trackBaseName % "." % qFormat); EXPECT_TRUE(img.save(cLoc_filename, format)); prefCovers.append(QFileInfo(cLoc_filename)); - res2 = CoverArtUtils::selectCoverArtForTrack(trackBaseName, trackAlbum, - prefCovers); + res2 = CoverArtUtils::selectCoverArtForTrack( + mixxx::FileInfo(trackBaseName), trackAlbum, prefCovers); expected2.setImage(QImage(cLoc_filename)); expected2.coverLocation = trackBaseName % ".jpg"; diff --git a/src/test/cuecontrol_test.cpp b/src/test/cuecontrol_test.cpp index 8439ecda9a0..2b8c1f6eb04 100644 --- a/src/test/cuecontrol_test.cpp +++ b/src/test/cuecontrol_test.cpp @@ -28,7 +28,8 @@ class CueControlTest : public BaseSignalPathTest { TrackPointer createTestTrack() const { const QString kTrackLocationTest = QDir::currentPath() + "/src/test/sine-30.wav"; - const auto pTrack = Track::newTemporary(kTrackLocationTest, SecurityTokenPointer()); + const auto pTrack = Track::newTemporary( + mixxx::FileAccess(mixxx::FileInfo(kTrackLocationTest))); pTrack->setAudioProperties( mixxx::audio::ChannelCount(2), mixxx::audio::SampleRate(44100), diff --git a/src/test/directorydaotest.cpp b/src/test/directorydaotest.cpp index 29f89f0ee51..05bb7948af8 100644 --- a/src/test/directorydaotest.cpp +++ b/src/test/directorydaotest.cpp @@ -196,26 +196,22 @@ TEST_F(DirectoryDAOTest, relocateDirectory) { // ok now lets create some tracks here ASSERT_TRUE(internalCollection() ->addTrack( - Track::newTemporary( - TrackFile(testdir, "a." + getSupportedFileExt())), + Track::newTemporary(testdir, "a." + getSupportedFileExt()), false) .isValid()); ASSERT_TRUE(internalCollection() ->addTrack( - Track::newTemporary( - TrackFile(testdir, "b." + getSupportedFileExt())), + Track::newTemporary(testdir, "b." + getSupportedFileExt()), false) .isValid()); ASSERT_TRUE(internalCollection() ->addTrack( - Track::newTemporary( - TrackFile(test2, "c." + getSupportedFileExt())), + Track::newTemporary(test2, "c." + getSupportedFileExt()), false) .isValid()); ASSERT_TRUE(internalCollection() ->addTrack( - Track::newTemporary( - TrackFile(test2, "d." + getSupportedFileExt())), + Track::newTemporary(test2, "d." + getSupportedFileExt()), false) .isValid()); diff --git a/src/test/globaltrackcache_test.cpp b/src/test/globaltrackcache_test.cpp index 7e98b4bae9e..64cc4594389 100644 --- a/src/test/globaltrackcache_test.cpp +++ b/src/test/globaltrackcache_test.cpp @@ -11,8 +11,8 @@ namespace { const QDir kTestDir(QDir::current().absoluteFilePath("src/test/id3-test-data")); -const TrackFile kTestFile(kTestDir.absoluteFilePath("cover-test.flac")); -const TrackFile kTestFile2(kTestDir.absoluteFilePath("cover-test.ogg")); +const mixxx::FileInfo kTestFile(kTestDir.absoluteFilePath("cover-test.flac")); +const mixxx::FileInfo kTestFile2(kTestDir.absoluteFilePath("cover-test.ogg")); class TrackTitleThread: public QThread { public: @@ -97,7 +97,8 @@ TEST_F(GlobalTrackCacheTest, resolveByFileInfo) { TrackPointer track; { - GlobalTrackCacheResolver resolver(kTestFile); + auto testFileAccess = mixxx::FileAccess(mixxx::FileInfo(kTestFile)); + GlobalTrackCacheResolver resolver(testFileAccess); track = resolver.getTrack(); EXPECT_TRUE(static_cast(track)); EXPECT_EQ(2, track.use_count()); @@ -159,7 +160,8 @@ TEST_F(GlobalTrackCacheTest, concurrentDelete) { TrackPointer track; { - GlobalTrackCacheResolver resolver(kTestFile); + auto testFileAccess = mixxx::FileAccess(mixxx::FileInfo(kTestFile)); + GlobalTrackCacheResolver resolver(testFileAccess); track = resolver.getTrack(); EXPECT_TRUE(static_cast(track)); trackId = track->getId(); @@ -200,10 +202,14 @@ TEST_F(GlobalTrackCacheTest, concurrentDelete) { TEST_F(GlobalTrackCacheTest, evictWhileMoving) { ASSERT_TRUE(GlobalTrackCacheLocker().isEmpty()); - TrackPointer track1 = GlobalTrackCacheResolver(kTestFile).getTrack(); + TrackPointer track1 = GlobalTrackCacheResolver( + mixxx::FileAccess(mixxx::FileInfo(kTestFile))) + .getTrack(); EXPECT_TRUE(static_cast(track1)); - TrackPointer track2 = GlobalTrackCacheResolver(kTestFile2).getTrack(); + TrackPointer track2 = GlobalTrackCacheResolver( + mixxx::FileAccess(mixxx::FileInfo(kTestFile2))) + .getTrack(); EXPECT_TRUE(static_cast(track2)); track1 = std::move(track2); diff --git a/src/test/hotcuecontrol_test.cpp b/src/test/hotcuecontrol_test.cpp index 26e2a949742..c45c951d528 100644 --- a/src/test/hotcuecontrol_test.cpp +++ b/src/test/hotcuecontrol_test.cpp @@ -41,7 +41,8 @@ class HotcueControlTest : public BaseSignalPathTest { TrackPointer createTestTrack() const { const QString kTrackLocationTest = QDir::currentPath() + "/src/test/sine-30.wav"; - const auto pTrack = Track::newTemporary(kTrackLocationTest, SecurityTokenPointer()); + const auto pTrack = Track::newTemporary( + mixxx::FileAccess(mixxx::FileInfo(kTrackLocationTest))); pTrack->setAudioProperties( mixxx::audio::ChannelCount(2), mixxx::audio::SampleRate(44100), diff --git a/src/test/librarytest.cpp b/src/test/librarytest.cpp index eb764bb375f..23048a411d5 100644 --- a/src/test/librarytest.cpp +++ b/src/test/librarytest.cpp @@ -37,5 +37,5 @@ LibraryTest::LibraryTest() TrackPointer LibraryTest::getOrAddTrackByLocation( const QString& trackLocation) const { return m_pTrackCollectionManager->getOrAddTrack( - TrackRef::fromFileInfo(trackLocation)); + TrackRef::fromFilePath(trackLocation)); } diff --git a/src/test/playlisttest.cpp b/src/test/playlisttest.cpp index 47a1792e991..3470403b49b 100644 --- a/src/test/playlisttest.cpp +++ b/src/test/playlisttest.cpp @@ -1,12 +1,11 @@ #include -#include #include #include +#include #include "library/parser.h" - class DummyParser : public Parser { public: QList parse(const QString&) override { @@ -16,7 +15,10 @@ class DummyParser : public Parser { QString playlistEntryToFilePath( const QString& playlistEntry, const QString& basePath = QString()) { - return playlistEntryToTrackFile(playlistEntry, basePath).asFileInfo().filePath(); + const auto fileInfo = playlistEntryToFileInfo(playlistEntry, basePath); + // Return the plain, literal file path, because the location + // is undefined if relative paths. + return fileInfo.asQFileInfo().filePath(); } }; diff --git a/src/test/soundproxy_test.cpp b/src/test/soundproxy_test.cpp index c08e4076530..51bf7369400 100644 --- a/src/test/soundproxy_test.cpp +++ b/src/test/soundproxy_test.cpp @@ -210,8 +210,7 @@ TEST_F(SoundSourceProxyTest, openEmptyFile) { } TEST_F(SoundSourceProxyTest, readArtist) { - auto pTrack = Track::newTemporary(TrackFile( - kTestDir, "artist.mp3")); + auto pTrack = Track::newTemporary(kTestDir, "artist.mp3"); SoundSourceProxy proxy(pTrack); proxy.updateTrackFromSource(); EXPECT_EQ("Test Artist", pTrack->getArtist()); @@ -221,8 +220,8 @@ TEST_F(SoundSourceProxyTest, readNoTitle) { // We need to verify every track has at least a title to not have empty lines in the library // Test a file with no metadata - auto pTrack1 = Track::newTemporary(TrackFile( - kTestDir, "empty.mp3")); + auto pTrack1 = Track::newTemporary( + kTestDir, "empty.mp3"); SoundSourceProxy proxy1(pTrack1); proxy1.updateTrackFromSource(); EXPECT_EQ("empty", pTrack1->getTitle()); @@ -233,8 +232,8 @@ TEST_F(SoundSourceProxyTest, readNoTitle) { EXPECT_EQ("empty", pTrack1->getTitle()); // Test a file with other metadata but no title - auto pTrack2 = Track::newTemporary(TrackFile( - kTestDir, "cover-test-png.mp3")); + auto pTrack2 = Track::newTemporary( + kTestDir, "cover-test-png.mp3"); SoundSourceProxy proxy2(pTrack2); proxy2.updateTrackFromSource(); EXPECT_EQ("cover-test-png", pTrack2->getTitle()); @@ -245,16 +244,15 @@ TEST_F(SoundSourceProxyTest, readNoTitle) { EXPECT_EQ("cover-test-png", pTrack2->getTitle()); // Test a file with a title - auto pTrack3 = Track::newTemporary(TrackFile( - kTestDir, "cover-test-jpg.mp3")); + auto pTrack3 = Track::newTemporary( + kTestDir, "cover-test-jpg.mp3"); SoundSourceProxy proxy3(pTrack3); proxy3.updateTrackFromSource(); EXPECT_EQ("test22kMono", pTrack3->getTitle()); } TEST_F(SoundSourceProxyTest, TOAL_TPE2) { - auto pTrack = Track::newTemporary(TrackFile( - kTestDir, "TOAL_TPE2.mp3")); + auto pTrack = Track::newTemporary(kTestDir, "TOAL_TPE2.mp3"); SoundSourceProxy proxy(pTrack); mixxx::TrackMetadata trackMetadata; EXPECT_EQ(mixxx::MetadataSource::ImportResult::Succeeded, proxy.importTrackMetadata(&trackMetadata)); diff --git a/src/test/trackdao_test.cpp b/src/test/trackdao_test.cpp index 1c062e00ea2..7e5a63c4b4a 100644 --- a/src/test/trackdao_test.cpp +++ b/src/test/trackdao_test.cpp @@ -15,13 +15,13 @@ TEST_F(TrackDAOTest, detectMovedTracks) { QString filename = QStringLiteral("file.mp3"); - TrackFile oldFile(QDir(QDir::tempPath() + QStringLiteral("/old/dir1")), filename); - TrackFile newFile(QDir(QDir::tempPath() + QStringLiteral("/new/dir1")), filename); - TrackFile otherFile(QDir(QDir::tempPath() + QStringLiteral("/new")), filename); + mixxx::FileInfo oldFile(QDir(QDir::tempPath() + QStringLiteral("/old/dir1")), filename); + mixxx::FileInfo newFile(QDir(QDir::tempPath() + QStringLiteral("/new/dir1")), filename); + mixxx::FileInfo otherFile(QDir(QDir::tempPath() + QStringLiteral("/new")), filename); - TrackPointer pOldTrack = Track::newTemporary(oldFile); - TrackPointer pNewTrack = Track::newTemporary(newFile); - TrackPointer pOtherTrack = Track::newTemporary(otherFile); + TrackPointer pOldTrack = Track::newTemporary(mixxx::FileAccess(oldFile)); + TrackPointer pNewTrack = Track::newTemporary(mixxx::FileAccess(newFile)); + TrackPointer pOtherTrack = Track::newTemporary(mixxx::FileAccess(otherFile)); // Arbitrary duration pOldTrack->setDuration(135); diff --git a/src/test/trackexport_test.cpp b/src/test/trackexport_test.cpp index d897eba5205..6cb459241ae 100644 --- a/src/test/trackexport_test.cpp +++ b/src/test/trackexport_test.cpp @@ -39,12 +39,12 @@ void FakeOverwriteAnswerer::cancelButtonClicked() { TEST_F(TrackExporterTest, SimpleListExport) { // Create a simple list of trackpointers and export them. - TrackFile fileinfo1(m_testDataDir.filePath("cover-test.ogg")); - TrackPointer track1(Track::newTemporary(fileinfo1)); - TrackFile fileinfo2(m_testDataDir.filePath("cover-test.flac")); - TrackPointer track2(Track::newTemporary(fileinfo2)); - TrackFile fileinfo3(m_testDataDir.filePath("cover-test-itunes-12.3.0-aac.m4a")); - TrackPointer track3(Track::newTemporary(fileinfo3)); + mixxx::FileInfo fileinfo1(m_testDataDir.filePath("cover-test.ogg")); + TrackPointer track1(Track::newTemporary(mixxx::FileAccess(fileinfo1))); + mixxx::FileInfo fileinfo2(m_testDataDir.filePath("cover-test.flac")); + TrackPointer track2(Track::newTemporary(mixxx::FileAccess(fileinfo2))); + mixxx::FileInfo fileinfo3(m_testDataDir.filePath("cover-test-itunes-12.3.0-aac.m4a")); + TrackPointer track3(Track::newTemporary(mixxx::FileAccess(fileinfo3))); // An initializer list would be prettier here, but it doesn't compile // on MSVC or OSX. @@ -70,11 +70,11 @@ TEST_F(TrackExporterTest, SimpleListExport) { TEST_F(TrackExporterTest, OverwriteSkip) { // Export a tracklist with two existing tracks -- overwrite one and skip // the other. - TrackFile fileinfo1(m_testDataDir.filePath("cover-test.ogg")); - const qint64 fileSize1 = fileinfo1.asFileInfo().size(); - TrackPointer track1(Track::newTemporary(fileinfo1)); - TrackFile fileinfo2(m_testDataDir.filePath("cover-test-itunes-12.3.0-aac.m4a")); - TrackPointer track2(Track::newTemporary(fileinfo2)); + mixxx::FileInfo fileinfo1(m_testDataDir.filePath("cover-test.ogg")); + const qint64 fileSize1 = fileinfo1.sizeInBytes(); + TrackPointer track1(Track::newTemporary(mixxx::FileAccess(fileinfo1))); + mixxx::FileInfo fileinfo2(m_testDataDir.filePath("cover-test-itunes-12.3.0-aac.m4a")); + TrackPointer track2(Track::newTemporary(mixxx::FileAccess(fileinfo2))); // Create empty versions at the destination so we can see if we actually // overwrote or skipped. @@ -115,12 +115,12 @@ TEST_F(TrackExporterTest, OverwriteSkip) { TEST_F(TrackExporterTest, OverwriteAll) { // Export a tracklist with two existing tracks -- overwrite both. - TrackFile fileinfo1(m_testDataDir.filePath("cover-test.ogg")); - const qint64 fileSize1 = fileinfo1.asFileInfo().size(); - TrackPointer track1(Track::newTemporary(fileinfo1)); - TrackFile fileinfo2(m_testDataDir.filePath("cover-test-itunes-12.3.0-aac.m4a")); - const qint64 fileSize2 = fileinfo2.asFileInfo().size(); - TrackPointer track2(Track::newTemporary(fileinfo2)); + mixxx::FileInfo fileinfo1(m_testDataDir.filePath("cover-test.ogg")); + const qint64 fileSize1 = fileinfo1.sizeInBytes(); + TrackPointer track1(Track::newTemporary(mixxx::FileAccess(fileinfo1))); + mixxx::FileInfo fileinfo2(m_testDataDir.filePath("cover-test-itunes-12.3.0-aac.m4a")); + const qint64 fileSize2 = fileinfo2.sizeInBytes(); + TrackPointer track2(Track::newTemporary(mixxx::FileAccess(fileinfo2))); // Create empty versions at the destination so we can see if we actually // overwrote or skipped. @@ -158,10 +158,10 @@ TEST_F(TrackExporterTest, OverwriteAll) { TEST_F(TrackExporterTest, SkipAll) { // Export a tracklist with two existing tracks -- skip both. - TrackFile fileinfo1(m_testDataDir.filePath("cover-test.ogg")); - TrackPointer track1(Track::newTemporary(fileinfo1)); - TrackFile fileinfo2(m_testDataDir.filePath("cover-test-itunes-12.3.0-aac.m4a")); - TrackPointer track2(Track::newTemporary(fileinfo2)); + mixxx::FileInfo fileinfo1(m_testDataDir.filePath("cover-test.ogg")); + TrackPointer track1(Track::newTemporary(mixxx::FileAccess(fileinfo1))); + mixxx::FileInfo fileinfo2(m_testDataDir.filePath("cover-test-itunes-12.3.0-aac.m4a")); + TrackPointer track2(Track::newTemporary(mixxx::FileAccess(fileinfo2))); // Create empty versions at the destination so we can see if we actually // overwrote or skipped. @@ -200,10 +200,10 @@ TEST_F(TrackExporterTest, SkipAll) { TEST_F(TrackExporterTest, Cancel) { // Export a tracklist with two existing tracks, but cancel before we do // anything. - TrackFile fileinfo1(m_testDataDir.filePath("cover-test.ogg")); - TrackPointer track1(Track::newTemporary(fileinfo1)); - TrackFile fileinfo2(m_testDataDir.filePath("cover-test-itunes-12.3.0-aac.m4a")); - TrackPointer track2(Track::newTemporary(fileinfo2)); + mixxx::FileInfo fileinfo1(m_testDataDir.filePath("cover-test.ogg")); + TrackPointer track1(Track::newTemporary(mixxx::FileAccess(fileinfo1))); + mixxx::FileInfo fileinfo2(m_testDataDir.filePath("cover-test-itunes-12.3.0-aac.m4a")); + TrackPointer track2(Track::newTemporary(mixxx::FileAccess(fileinfo2))); // Create empty version at the destination so we can see if we actually // canceled. @@ -238,9 +238,9 @@ TEST_F(TrackExporterTest, Cancel) { TEST_F(TrackExporterTest, DedupeList) { // Create a track list with a duplicate track, see that it gets deduped. - TrackFile fileinfo1(m_testDataDir.filePath("cover-test.ogg")); - TrackPointer track1(Track::newTemporary(fileinfo1)); - TrackPointer track2(Track::newTemporary(fileinfo1)); + mixxx::FileInfo fileinfo1(m_testDataDir.filePath("cover-test.ogg")); + TrackPointer track1(Track::newTemporary(mixxx::FileAccess(fileinfo1))); + TrackPointer track2(Track::newTemporary(mixxx::FileAccess(fileinfo1))); // Set up the worker and answerer. TrackPointerList tracks; @@ -267,17 +267,17 @@ TEST_F(TrackExporterTest, DedupeList) { TEST_F(TrackExporterTest, MungeFilename) { // Create a track list with a duplicate track in a different location, // see that the name gets munged. - TrackFile fileinfo1(m_testDataDir.filePath("cover-test.ogg")); - TrackPointer track1(Track::newTemporary(fileinfo1)); + mixxx::FileInfo fileinfo1(m_testDataDir.filePath("cover-test.ogg")); + TrackPointer track1(Track::newTemporary(mixxx::FileAccess(fileinfo1))); // Create a file with the same name in a different place. Its filename // should be munged and the file still copied. QDir tempPath(QDir::tempPath()); QFile file2(tempPath.filePath("cover-test.ogg")); - TrackFile fileinfo2(file2); + mixxx::FileInfo fileinfo2(file2); ASSERT_TRUE(file2.open(QIODevice::WriteOnly)); file2.close(); - TrackPointer track2(Track::newTemporary(fileinfo2)); + TrackPointer track2(Track::newTemporary(mixxx::FileAccess(fileinfo2))); // Set up the worker and answerer. TrackPointerList tracks; diff --git a/src/test/trackreftest.cpp b/src/test/trackreftest.cpp index edace559c34..f0735f5b386 100644 --- a/src/test/trackreftest.cpp +++ b/src/test/trackreftest.cpp @@ -31,17 +31,17 @@ class TrackRefTest : public testing::Test { virtual void TearDown() { } - static void verifyFileInfo(const TrackRef& actual, const TrackFile& trackFile) { + static void verifyFileInfo(const TrackRef& actual, const mixxx::FileInfo& fileInfo) { EXPECT_TRUE(actual.hasLocation()); - EXPECT_EQ(trackFile.location(), actual.getLocation()); + EXPECT_EQ(fileInfo.location(), actual.getLocation()); EXPECT_TRUE(actual.hasCanonicalLocation()); - EXPECT_EQ(trackFile.canonicalLocation(), actual.getCanonicalLocation()); + EXPECT_EQ(fileInfo.canonicalLocation(), actual.getCanonicalLocation()); } const QTemporaryFile m_tempFile; const QDir m_tempFileDir; const QString m_tempFileName; - const TrackFile m_tempFileInfo; + const mixxx::FileInfo m_tempFileInfo; const TrackId m_validTrackId; const TrackId m_invalidTrackId; }; diff --git a/src/test/trackupdate_test.cpp b/src/test/trackupdate_test.cpp index 011838625f6..64c9e75daab 100644 --- a/src/test/trackupdate_test.cpp +++ b/src/test/trackupdate_test.cpp @@ -24,8 +24,7 @@ class TrackUpdateTest: public MixxxTest { } static TrackPointer newTestTrack() { - return Track::newTemporary(TrackFile( - kTestDir, "TOAL_TPE2.mp3")); + return Track::newTemporary(kTestDir, "TOAL_TPE2.mp3"); } static TrackPointer newTestTrackParsed() { diff --git a/src/track/globaltrackcache.cpp b/src/track/globaltrackcache.cpp index 26cf3af65d3..fccc4e88210 100644 --- a/src/track/globaltrackcache.cpp +++ b/src/track/globaltrackcache.cpp @@ -179,20 +179,18 @@ QSet GlobalTrackCacheLocker::getCachedTrackIds() const { } GlobalTrackCacheResolver::GlobalTrackCacheResolver( - TrackFile fileInfo, - SecurityTokenPointer pSecurityToken) + mixxx::FileAccess fileAccess) : m_lookupResult(GlobalTrackCacheLookupResult::None) { DEBUG_ASSERT(m_pInstance); - m_pInstance->resolve(this, std::move(fileInfo), TrackId(), std::move(pSecurityToken)); + m_pInstance->resolve(this, std::move(fileAccess), TrackId()); } GlobalTrackCacheResolver::GlobalTrackCacheResolver( - TrackFile fileInfo, - TrackId trackId, - SecurityTokenPointer pSecurityToken) + mixxx::FileAccess fileAccess, + TrackId trackId) : m_lookupResult(GlobalTrackCacheLookupResult::None) { DEBUG_ASSERT(m_pInstance); - m_pInstance->resolve(this, std::move(fileInfo), std::move(trackId), std::move(pSecurityToken)); + m_pInstance->resolve(this, std::move(fileAccess), std::move(trackId)); } void GlobalTrackCacheResolver::initLookupResult( @@ -335,20 +333,20 @@ void GlobalTrackCache::relocateTracks( ++i) { const QString oldCanonicalLocation = i->first; Track* plainPtr = i->second->getPlainPtr(); - auto fileInfo = plainPtr->getFileInfo(); + auto fileAccess = plainPtr->getFileAccess(); TrackRef trackRef = TrackRef::fromFileInfo( - fileInfo, + fileAccess.info(), plainPtr->getId()); if (!trackRef.hasCanonicalLocation() && trackRef.hasId() && pRelocator) { - auto relocatedFileInfo = pRelocator->relocateCachedTrack( - trackRef.getId(), - fileInfo); - if (fileInfo != relocatedFileInfo) { - plainPtr->relocate(relocatedFileInfo); + auto relocatedFileAccess = pRelocator->relocateCachedTrack( + trackRef.getId(), + fileAccess); + if (fileAccess.info() != relocatedFileAccess.info()) { + plainPtr->relocate(relocatedFileAccess); trackRef = TrackRef::fromFileInfo( - relocatedFileInfo, + relocatedFileAccess.info(), trackRef.getId()); - fileInfo = std::move(relocatedFileInfo); + fileAccess = std::move(relocatedFileAccess); } } if (!trackRef.hasCanonicalLocation()) { @@ -414,7 +412,7 @@ void GlobalTrackCache::deactivate() { auto i = m_tracksById.begin(); Track* plainPtr= i->second->getPlainPtr(); saveEvictedTrack(plainPtr); - m_tracksByCanonicalLocation.erase(plainPtr->getCanonicalLocation()); + m_tracksByCanonicalLocation.erase(plainPtr->getFileInfo().canonicalLocation()); m_tracksById.erase(i); } @@ -557,9 +555,8 @@ TrackPointer GlobalTrackCache::revive( void GlobalTrackCache::resolve( GlobalTrackCacheResolver* /*in/out*/ pCacheResolver, - TrackFile /*in*/ fileInfo, - TrackId trackId, - SecurityTokenPointer pSecurityToken) { + mixxx::FileAccess /*in*/ fileAccess, + TrackId trackId) { DEBUG_ASSERT(pCacheResolver); // Primary lookup by id (if available) if (trackId.isValid()) { @@ -587,7 +584,7 @@ void GlobalTrackCache::resolve( // Secondary lookup by canonical location // The TrackRef is constructed now after the lookup by ID failed to // avoid calculating the canonical file path if it is not needed. - TrackRef trackRef = TrackRef::fromFileInfo(fileInfo, trackId); + TrackRef trackRef = TrackRef::fromFileInfo(fileAccess.info(), trackId); if (trackRef.hasCanonicalLocation()) { if (debugLogEnabled()) { kLogger.debug() @@ -638,8 +635,7 @@ void GlobalTrackCache::resolve( } auto deletingPtr = std::unique_ptr( new Track( - std::move(fileInfo), - std::move(pSecurityToken), + std::move(fileAccess), std::move(trackId)), GlobalTrackCacheEntry::TrackDeleter(m_deleteTrackFn)); diff --git a/src/track/globaltrackcache.h b/src/track/globaltrackcache.h index faa3d2b8001..6a423f712bf 100644 --- a/src/track/globaltrackcache.h +++ b/src/track/globaltrackcache.h @@ -26,12 +26,12 @@ class /*interface*/ GlobalTrackCacheRelocator { friend class GlobalTrackCache; // Try to determine and return the relocated file info // or otherwise return just the provided file info. - virtual TrackFile relocateCachedTrack( + virtual mixxx::FileAccess relocateCachedTrack( TrackId trackId, - TrackFile fileInfo) = 0; + mixxx::FileAccess fileAccess) = 0; -protected: - virtual ~GlobalTrackCacheRelocator() = default; + protected: + virtual ~GlobalTrackCacheRelocator() = default; }; typedef void (*deleteTrackFn_t)(Track*); @@ -133,19 +133,17 @@ class GlobalTrackCacheLocker { class GlobalTrackCacheResolver final: public GlobalTrackCacheLocker { public: - GlobalTrackCacheResolver( - TrackFile fileInfo, - SecurityTokenPointer pSecurityToken = SecurityTokenPointer()); - GlobalTrackCacheResolver( - TrackFile fileInfo, - TrackId trackId, - SecurityTokenPointer pSecurityToken = SecurityTokenPointer()); - GlobalTrackCacheResolver(const GlobalTrackCacheResolver&) = delete; - GlobalTrackCacheResolver(GlobalTrackCacheResolver&&) = default; - - GlobalTrackCacheLookupResult getLookupResult() const { - return m_lookupResult; - } + GlobalTrackCacheResolver( + mixxx::FileAccess fileAccess); + GlobalTrackCacheResolver( + mixxx::FileAccess fileAccess, + TrackId trackId); + GlobalTrackCacheResolver(const GlobalTrackCacheResolver&) = delete; + GlobalTrackCacheResolver(GlobalTrackCacheResolver&&) = default; + + GlobalTrackCacheLookupResult getLookupResult() const { + return m_lookupResult; + } const TrackPointer& getTrack() const { return m_strongPtr; @@ -258,9 +256,8 @@ class GlobalTrackCache : public QObject { void resolve( GlobalTrackCacheResolver* /*in/out*/ pCacheResolver, - TrackFile /*in*/ fileInfo, - TrackId /*in*/ trackId, - SecurityTokenPointer /*in*/ pSecurityToken); + mixxx::FileAccess /*in*/ fileAccess, + TrackId /*in*/ trackId); TrackRef initTrackId( const TrackPointer& strongPtr, diff --git a/src/track/track.cpp b/src/track/track.cpp index cabb2af396e..3e4ed0592af 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -23,16 +23,6 @@ const ConfigKey kConfigKeySeratoMetadataExport("[Library]", "SeratoMetadataExpor // memory leaks. std::atomic s_numberOfInstances; -SecurityTokenPointer openSecurityToken( - const TrackFile& trackFile, - SecurityTokenPointer pSecurityToken = SecurityTokenPointer()) { - if (pSecurityToken.isNull()) { - return Sandbox::openSecurityToken(trackFile.asFileInfo(), true); - } else { - return pSecurityToken; - } -} - template inline bool compareAndSet(T* pField, const T& value) { if (*pField != value) { @@ -58,12 +48,10 @@ inline mixxx::Bpm getBeatsPointerBpm( const QString Track::kArtistTitleSeparator = QStringLiteral(" - "); Track::Track( - TrackFile fileInfo, - SecurityTokenPointer pSecurityToken, + mixxx::FileAccess fileAccess, TrackId trackId) : m_qMutex(QMutex::Recursive), - m_fileInfo(std::move(fileInfo)), - m_pSecurityToken(openSecurityToken(m_fileInfo, std::move(pSecurityToken))), + m_fileAccess(std::move(fileAccess)), m_record(trackId), m_bDirty(false), m_bMarkedForMetadataExport(false) { @@ -102,29 +90,24 @@ Track::~Track() { //static TrackPointer Track::newTemporary( - TrackFile fileInfo, - SecurityTokenPointer pSecurityToken) { + mixxx::FileAccess fileAccess) { return std::make_shared( - std::move(fileInfo), - std::move(pSecurityToken)); + std::move(fileAccess)); } //static TrackPointer Track::newDummy( - TrackFile fileInfo, + const QString& filePath, TrackId trackId) { return std::make_shared( - std::move(fileInfo), - SecurityTokenPointer(), + mixxx::FileAccess(mixxx::FileInfo(filePath)), trackId); } void Track::relocate( - TrackFile fileInfo, - SecurityTokenPointer pSecurityToken) { + mixxx::FileAccess fileAccess) { QMutexLocker lock(&m_qMutex); - m_pSecurityToken = openSecurityToken(fileInfo, std::move(pSecurityToken)); - m_fileInfo = std::move(fileInfo); + m_fileAccess = std::move(fileAccess); // The track does not need to be marked as dirty, // because this function will always be called with // the updated location from the database. @@ -271,11 +254,6 @@ void Track::readTrackRecord( } } -QString Track::getCanonicalLocation() const { - QMutexLocker lock(&m_qMutex); - return /*mutable*/ m_fileInfo.freshCanonicalLocation(); // non-const -} - mixxx::ReplayGain Track::getReplayGain() const { QMutexLocker lock(&m_qMutex); return m_record.getMetadata().getTrackInfo().getReplayGain(); @@ -417,7 +395,7 @@ QString Track::getInfo() const { QMutexLocker lock(&m_qMutex); if (m_record.getMetadata().getTrackInfo().getArtist().trimmed().isEmpty()) { if (m_record.getMetadata().getTrackInfo().getTitle().trimmed().isEmpty()) { - return m_fileInfo.fileName(); + return m_fileAccess.info().fileName(); } else { return m_record.getMetadata().getTrackInfo().getTitle(); } @@ -432,7 +410,7 @@ QString Track::getTitleInfo() const { QMutexLocker lock(&m_qMutex); if (m_record.getMetadata().getTrackInfo().getArtist().trimmed().isEmpty() && m_record.getMetadata().getTrackInfo().getTitle().trimmed().isEmpty()) { - return m_fileInfo.fileName(); + return m_fileAccess.info().fileName(); } else { return m_record.getMetadata().getTrackInfo().getTitle(); } @@ -1326,10 +1304,10 @@ bool Track::refreshCoverImageDigest( QMutexLocker lock(&m_qMutex); auto coverInfo = CoverInfo( m_record.getCoverInfo(), - m_fileInfo.location()); + m_fileAccess.info().location()); if (!coverInfo.refreshImageDigest( loadedImage, - m_pSecurityToken)) { + m_fileAccess.token())) { return false; } if (!compareAndSet( @@ -1339,7 +1317,7 @@ bool Track::refreshCoverImageDigest( } kLogger.info() << "Refreshed cover image digest" - << m_fileInfo.location(); + << m_fileAccess.info().location(); markDirtyAndUnlock(&lock); emit coverArtUpdated(); return true; @@ -1352,7 +1330,7 @@ CoverInfoRelative Track::getCoverInfo() const { CoverInfo Track::getCoverInfoWithLocation() const { QMutexLocker lock(&m_qMutex); - return CoverInfo(m_record.getCoverInfo(), m_fileInfo.location()); + return CoverInfo(m_record.getCoverInfo(), m_fileAccess.info().location()); } ExportTrackMetadataResult Track::exportMetadata( diff --git a/src/track/track.h b/src/track/track.h index 650adc5d5e9..f64517242c8 100644 --- a/src/track/track.h +++ b/src/track/track.h @@ -12,17 +12,16 @@ #include "track/cue.h" #include "track/cueinfoimporter.h" #include "track/track_decl.h" -#include "track/trackfile.h" #include "track/trackrecord.h" -#include "util/sandbox.h" +#include "util/fileaccess.h" +#include "util/memory.h" #include "waveform/waveform.h" class Track : public QObject { Q_OBJECT public: - Track(TrackFile fileInfo, - SecurityTokenPointer pSecurityToken, + Track(mixxx::FileAccess fileAccess, TrackId trackId = TrackId()); Track(const Track&) = delete; ~Track() override; @@ -34,11 +33,20 @@ class Track : public QObject { // Use SoundSourceProxy::importTemporaryTrack() for importing files // to ensure that the file will not be written while reading it! static TrackPointer newTemporary( - TrackFile fileInfo = TrackFile(), - SecurityTokenPointer pSecurityToken = SecurityTokenPointer()); + mixxx::FileAccess fileAccess = mixxx::FileAccess()); + static TrackPointer newTemporary( + const QString& filePath) { + return newTemporary(mixxx::FileAccess(mixxx::FileInfo(filePath))); + } + static TrackPointer newTemporary( + const QDir& dir, + const QString& file) { + return newTemporary(mixxx::FileAccess(mixxx::FileInfo(dir, file))); + } + // Creates a dummy instance only for testing purposes. static TrackPointer newDummy( - TrackFile fileInfo, + const QString& filePath, TrackId trackId); Q_PROPERTY(QString artist READ getArtist WRITE setArtist) @@ -63,26 +71,22 @@ class Track : public QObject { Q_PROPERTY(QString info READ getInfo STORED false) Q_PROPERTY(QString titleInfo READ getTitleInfo STORED false) - TrackFile getFileInfo() const { - // Copying TrackFile/QFileInfo is thread-safe (implicit sharing), no locking needed. - return m_fileInfo; + mixxx::FileAccess getFileAccess() const { + // Copying QFileInfo is thread-safe due to implicit sharing, + // i.e. no locking needed. + return m_fileAccess; } - SecurityTokenPointer getSecurityToken() const { - // Copying a QSharedPointer is thread-safe, no locking needed. - return m_pSecurityToken; + mixxx::FileInfo getFileInfo() const { + // Copying QFileInfo is thread-safe due to implicit sharing, + // i.e. no locking needed. + return m_fileAccess.info(); } TrackId getId() const; // Returns absolute path to the file, including the filename. QString getLocation() const { - return m_fileInfo.location(); - } - // The (refreshed) canonical location - QString getCanonicalLocation() const; - // Checks if the file exists - bool checkFileExists() const { - return m_fileInfo.checkFileExists(); + return m_fileAccess.info().location(); } // File/format type @@ -392,9 +396,7 @@ class Track : public QObject { /// Only used by GlobalTrackCacheResolver when the track is purged from the library void resetId(); - void relocate( - TrackFile fileInfo, - SecurityTokenPointer pSecurityToken = SecurityTokenPointer()); + void relocate(mixxx::FileAccess fileAccess); // Set whether the TIO is dirty or not and unlock before emitting // any signals. This must only be called from member functions @@ -474,9 +476,7 @@ class Track : public QObject { mutable QMutex m_qMutex; // The file - mutable TrackFile m_fileInfo; - - SecurityTokenPointer m_pSecurityToken; + mixxx::FileAccess m_fileAccess; mixxx::TrackRecord m_record; diff --git a/src/track/trackfile.cpp b/src/track/trackfile.cpp deleted file mode 100644 index e85bcf91d04..00000000000 --- a/src/track/trackfile.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "track/trackfile.h" - -QString TrackFile::freshCanonicalLocation() { - // Note: We return here the cached value, that was calculated just after - // init this TrackFile object. This will avoid repeated use of the time - // consuming file IO. - QString loc = canonicalLocation(); - if (loc.isEmpty()) { - // Refresh the cached file info and try again - m_fileInfo.refresh(); - loc = canonicalLocation(); - } - return loc; -} diff --git a/src/track/trackfile.h b/src/track/trackfile.h deleted file mode 100644 index 5a5c411caef..00000000000 --- a/src/track/trackfile.h +++ /dev/null @@ -1,132 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "util/fileinfo.h" - -// Wrapper class for dealing with track files and their -// path/location, URL, and URI representations. -// -// All file-related track properties are snapshots from the provided -// QFileInfo. Obtaining them might involve accessing the file system -// and should be used consciously! The QFileInfo class does some -// caching behind the scenes. -// -// Please note that the canonical location of QFileInfo may change at -// any time, when the underlying file system structure is modified. -// It becomes empty if the file is deleted. -// -// Copying an instance of this class is thread-safe, because the -// underlying QFileInfo is implicitly shared. -// -// TODO: Replace with mixxx::FileInfo -class TrackFile { - public: - static TrackFile fromUrl(const QUrl& url) { - return TrackFile(url.toLocalFile()); - } - - TrackFile() = default; - // For backward-compatibility the QString single argument - // constructor has not been declared as "explicit". It is - // also not strictly necessary and might be removed at some - // point to prevent unintended type conversions. - /*non-explicit*/ TrackFile( - const QString& filePath) - : m_fileInfo(filePath) { - } - explicit TrackFile( - QFileInfo fileInfo) - : m_fileInfo(std::move(fileInfo)) { - } - explicit TrackFile( - const QDir& dir, - const QString& file = QString()) - : m_fileInfo(QFileInfo(dir, file)) { - } - explicit TrackFile( - const mixxx::FileInfo& fileInfo) - : m_fileInfo(fileInfo.asQFileInfo()) { - } - - const QFileInfo& asFileInfo() const { - return m_fileInfo; - } - - // Local file representation - QString location() const { - return m_fileInfo.absoluteFilePath(); - } - QString canonicalLocation() const { - return m_fileInfo.canonicalFilePath(); - } - QString directory() const { - return m_fileInfo.absolutePath(); - } - QString baseName() const { - return m_fileInfo.baseName(); - } - QString fileName() const { - return m_fileInfo.fileName(); - } - qint64 fileSize() const { - return m_fileInfo.size(); - } - - QDateTime fileCreated() const { - return m_fileInfo.birthTime(); - } - QDateTime fileLastModified() const { - return m_fileInfo.lastModified(); - } - - // Check if the file actually exists on the file system. - bool checkFileExists() const { - return QFile::exists(location()); - } - - void refresh() { - m_fileInfo.refresh(); - } - - // Refresh the canonical location if it is still empty, i.e. if - // the file may have re-appeared after mounting the corresponding - // drive while Mixxx is already running. - // - // We ignore the case when the user changes a symbolic link to - // point a file to an other location, since this is a user action. - // We also don't care if a file disappears while Mixxx is running. - // Opening a non-existent file is already handled and doesn't cause - // any malfunction. - QString freshCanonicalLocation(); - - // Portable URL representation - QUrl toUrl() const { - return QUrl::fromLocalFile(location()); - } - - friend bool operator==(const TrackFile& lhs, const TrackFile& rhs) { - return lhs.m_fileInfo == rhs.m_fileInfo; - } - - private: - QFileInfo m_fileInfo; -}; - -inline bool operator!=(const TrackFile& lhs, const TrackFile& rhs) { - return !(lhs == rhs); -} - -inline QDebug operator<<(QDebug debug, const TrackFile& trackFile) { - return debug << trackFile.asFileInfo(); -} - -inline uint qHash( - const TrackFile& key, - uint seed = 0) { - return qHash(key.location(), seed); -} diff --git a/src/track/trackref.h b/src/track/trackref.h index ef238dad0e6..4288a0c669b 100644 --- a/src/track/trackref.h +++ b/src/track/trackref.h @@ -1,8 +1,7 @@ #pragma once #include "track/trackid.h" -#include "track/trackfile.h" - +#include "util/fileinfo.h" // A track in the library is identified by a location and an id. // The location is mandatory to identify the file, whereas the id @@ -11,15 +10,28 @@ // This class is intended to be used as a simple, almost immutable // value object. Only the id can be set once. class TrackRef final { -public: - // Converts a TrackFile and an optional TrackId into a TrackRef. This - // involves obtaining the file-related track properties from QFileInfo - // (see above) and should used consciously! The file info is refreshed - // implicitly if the canonical location if necessary. + public: + /// Converts a file path and an optional TrackId into a TrackRef. + /// + /// This involves and intermediate creation of mixxx::FileInfo + /// and accessing the file system! + static TrackRef fromFilePath( + const QString& filePath, + TrackId id = TrackId()) { + return fromFileInfo(mixxx::FileInfo(filePath), id); + } + + /// Converts a mixxx::FileInfo and an optional TrackId into a TrackRef. + /// + /// This involves obtaining the file-related track properties from + /// the file info and might involve accessing the file system! static TrackRef fromFileInfo( - TrackFile fileInfo, + mixxx::FileInfo fileInfo, TrackId id = TrackId()) { - auto canonicalLocation = fileInfo.freshCanonicalLocation(); + // The conditional refresh ensures that files that were previously + // unavailable (e.g. file system volume not mounted before) are + // resolved successfully. + auto canonicalLocation = fileInfo.resolveCanonicalLocation(); // All properties of the file info are now considered fresh return TrackRef( fileInfo.location(), diff --git a/src/util/dnd.cpp b/src/util/dnd.cpp index bb9d5b05915..0e3a9f00866 100644 --- a/src/util/dnd.cpp +++ b/src/util/dnd.cpp @@ -8,7 +8,6 @@ #include "mixer/playermanager.h" #include "sources/soundsourceproxy.h" #include "track/track.h" -#include "util/sandbox.h" namespace { @@ -33,28 +32,23 @@ QDrag* dragUrls( } bool addFileToList( - TrackFile trackFile, - QList* trackFiles) { - // Since the user just dropped these files into Mixxx we have permission - // to touch the file. Create a security token to keep this permission - // across reboots. - Sandbox::createSecurityToken(trackFile.asFileInfo()); - - if (!trackFile.checkFileExists()) { + mixxx::FileInfo fileInfo, + QList* fileInfos) { + if (!fileInfo.checkFileExists()) { return false; } // Filter out invalid URLs (eg. files that aren't supported audio // filetypes, etc.) - if (!SoundSourceProxy::isFileSupported(trackFile.asFileInfo())) { + if (!SoundSourceProxy::isFileSupported(fileInfo)) { return false; } - trackFiles->append(std::move(trackFile)); + fileInfos->append(std::move(fileInfo)); return true; } -QList dropEventFiles( +QList dropEventFiles( const QMimeData& mimeData, const QString& sourceIdentifier, bool firstOnly, @@ -64,7 +58,7 @@ QList dropEventFiles( if (!mimeData.hasUrls() || (mimeData.hasText() && mimeData.text() == sourceIdentifier)) { - return QList(); + return {}; } return DragAndDropHelper::supportedTracksFromUrls( @@ -89,20 +83,20 @@ bool allowLoadToPlayer( } return pConfig->getValueString( - ConfigKey("[Controls]", - "AllowTrackLoadToPlayingDeck")).toInt(); + ConfigKey("[Controls]", + "AllowTrackLoadToPlayingDeck")) + .toInt(); } } // anonymous namespace //static -QList DragAndDropHelper::supportedTracksFromUrls( +QList DragAndDropHelper::supportedTracksFromUrls( const QList& urls, bool firstOnly, bool acceptPlaylists) { - QList trackFiles; + QList fileInfos; for (const QUrl& url : urls) { - // XXX: Possible WTF alert - Previously we thought we needed // toString() here but what you actually want in any case when // converting a QUrl to a file system path is @@ -127,24 +121,24 @@ QList DragAndDropHelper::supportedTracksFromUrls( QScopedPointer playlist_parser(new ParserM3u()); QList track_list = playlist_parser->parse(file); foreach (const QString& playlistFile, track_list) { - addFileToList(TrackFile(playlistFile), &trackFiles); + addFileToList(mixxx::FileInfo(playlistFile), &fileInfos); } } else if (acceptPlaylists && url.toString().endsWith(".pls")) { QScopedPointer playlist_parser(new ParserPls()); QList track_list = playlist_parser->parse(file); foreach (const QString& playlistFile, track_list) { - addFileToList(TrackFile(playlistFile), &trackFiles); + addFileToList(mixxx::FileInfo(playlistFile), &fileInfos); } } else { - addFileToList(TrackFile::fromUrl(url), &trackFiles); + addFileToList(mixxx::FileInfo::fromQUrl(url), &fileInfos); } - if (firstOnly && !trackFiles.isEmpty()) { + if (firstOnly && !fileInfos.isEmpty()) { break; } } - return trackFiles; + return fileInfos; } //static @@ -180,7 +174,7 @@ bool DragAndDropHelper::dragEnterAccept( bool acceptPlaylists) { // TODO(XXX): This operation blocks the UI when many // files are selected! - QList files = dropEventFiles(mimeData, sourceIdentifier, firstOnly, acceptPlaylists); + const auto files = dropEventFiles(mimeData, sourceIdentifier, firstOnly, acceptPlaylists); return !files.isEmpty(); } @@ -190,7 +184,7 @@ QDrag* DragAndDropHelper::dragTrack( QWidget* pDragSource, const QString& sourceIdentifier) { QList trackUrls; - trackUrls.append(pTrack->getFileInfo().toUrl()); + trackUrls.append(pTrack->getFileInfo().toQUrl()); return dragUrls(trackUrls, pDragSource, sourceIdentifier); } @@ -201,7 +195,7 @@ QDrag* DragAndDropHelper::dragTrackLocations( const QString& sourceIdentifier) { QList trackUrls; foreach (QString location, locations) { - trackUrls.append(TrackFile(location).toUrl()); + trackUrls.append(mixxx::FileInfo(location).toQUrl()); } return dragUrls(trackUrls, pDragSource, sourceIdentifier); } @@ -232,7 +226,7 @@ void DragAndDropHelper::handleTrackDropEvent( target.emitCloneDeck(event->mimeData()->text(), group); return; } else { - QList files = dropEventFiles( + const QList files = dropEventFiles( *event->mimeData(), group, true, false); if (!files.isEmpty()) { event->accept(); diff --git a/src/util/dnd.h b/src/util/dnd.h index efd4d40e37e..d66a2472919 100644 --- a/src/util/dnd.h +++ b/src/util/dnd.h @@ -9,14 +9,14 @@ #include "preferences/usersettings.h" #include "track/track_decl.h" -#include "track/trackfile.h" +#include "util/fileinfo.h" #include "widget/trackdroptarget.h" class DragAndDropHelper final { public: DragAndDropHelper() = delete; - static QList supportedTracksFromUrls( + static QList supportedTracksFromUrls( const QList& urls, bool firstOnly, bool acceptPlaylists); diff --git a/src/util/fileaccess.cpp b/src/util/fileaccess.cpp index 1ff63f61a0f..0ad7ea61330 100644 --- a/src/util/fileaccess.cpp +++ b/src/util/fileaccess.cpp @@ -6,11 +6,11 @@ namespace { inline SecurityTokenPointer acquireSecurityToken( SecurityTokenPointer pSecurityToken, - const FileInfo& fileInfo) { + FileInfo* pFileInfo) { if (pSecurityToken) { return pSecurityToken; } else { - return Sandbox::openSecurityToken(fileInfo.asQFileInfo(), true); + return Sandbox::openSecurityToken(pFileInfo, true); } } @@ -23,7 +23,7 @@ FileAccess::FileAccess( m_pSecurityToken( acquireSecurityToken( std::move(pSecurityToken), - m_fileInfo)) { + &m_fileInfo)) { } } // namespace mixxx diff --git a/src/util/sandbox.cpp b/src/util/sandbox.cpp index bc9d22eed7a..6c35c75a4e0 100644 --- a/src/util/sandbox.cpp +++ b/src/util/sandbox.cpp @@ -58,29 +58,49 @@ void Sandbox::shutdown() { } } -// static -bool Sandbox::askForAccess(const QString& canonicalPath) { - if (sDebug) { - qDebug() << "Sandbox::askForAccess" << canonicalPath; +//static +bool Sandbox::createSecurityToken(mixxx::FileInfo* pFileInfo) { + VERIFY_OR_DEBUG_ASSERT(pFileInfo) { + return false; } - if (!enabled()) { - // Pretend we have access. - return true; + const auto canonicalLocation = pFileInfo->resolveCanonicalLocation(); + return createSecurityToken(canonicalLocation, pFileInfo->isDir()); +} + +//static +bool Sandbox::canAccess(mixxx::FileInfo* pFileInfo) { + VERIFY_OR_DEBUG_ASSERT(pFileInfo) { + return false; } + openSecurityToken(pFileInfo, true); + return pFileInfo->isReadable(); +} + +//static +bool Sandbox::canAccessDir(const QDir& dir) { + openSecurityTokenForDir(dir, true); + return QFileInfo(dir.canonicalPath()).isReadable(); +} - QFileInfo info(canonicalPath); +// static +bool Sandbox::askForAccess(mixxx::FileInfo* pFileInfo) { // We always want read/write access because we wouldn't want to have to // re-ask for access in the future if we need to write. - if (canAccess(info)) { + if (canAccess(pFileInfo)) { return true; } - if (sDebug) { - qDebug() << "Sandbox: Requesting user access to" << canonicalPath; + const QString canonicalLocation = pFileInfo->canonicalLocation(); + if (canonicalLocation.isEmpty()) { + // File does not exist + return false; } - QString title = QObject::tr("Mixxx Needs Access to: %1") - .arg(info.fileName()); + if (sDebug) { + qDebug() << "Sandbox: Requesting user access to" << canonicalLocation; + } + const QString fileName = pFileInfo->fileName(); + const QString title = QObject::tr("Mixxx Needs Access to: %1").arg(fileName); QMessageBox::question(nullptr, title, QObject::tr( @@ -94,20 +114,20 @@ bool Sandbox::askForAccess(const QString& canonicalPath) { "the file picker. " "We're sorry for this inconvenience.\n\n" "To abort this action, press Cancel on the file dialog.") - .arg(canonicalPath, info.fileName())); + .arg(canonicalLocation, fileName)); - QString result; - QFileInfo resultInfo; + mixxx::FileInfo resultInfo; while (true) { - if (info.isFile()) { - result = QFileDialog::getOpenFileName(nullptr, title, canonicalPath); - } else if (info.isDir()) { - result = QFileDialog::getExistingDirectory(nullptr, title, canonicalPath); + QString result; + if (pFileInfo->isFile()) { + result = QFileDialog::getOpenFileName(nullptr, title, canonicalLocation); + } else if (pFileInfo->isDir()) { + result = QFileDialog::getExistingDirectory(nullptr, title, canonicalLocation); } if (result.isNull()) { if (sDebug) { - qDebug() << "Sandbox: User rejected access to" << canonicalPath; + qDebug() << "Sandbox: User rejected access to" << canonicalLocation; } return false; } @@ -115,8 +135,8 @@ bool Sandbox::askForAccess(const QString& canonicalPath) { if (sDebug) { qDebug() << "Sandbox: User selected" << result; } - resultInfo = QFileInfo(result); - if (resultInfo == info) { + resultInfo = mixxx::FileInfo(result); + if (resultInfo == *pFileInfo) { break; } @@ -127,10 +147,9 @@ bool Sandbox::askForAccess(const QString& canonicalPath) { nullptr, title, QObject::tr("You selected the wrong file. To grant Mixxx access, " "please select the file '%1'. If you do not want to " "continue, press Cancel.") - .arg(info.fileName())); + .arg(fileName)); } - - return createSecurityToken(resultInfo); + return createSecurityToken(&resultInfo); } // static @@ -192,18 +211,21 @@ bool Sandbox::createSecurityToken(const QString& canonicalPath, } // static -SecurityTokenPointer Sandbox::openSecurityToken(const QFileInfo& file, bool create) { - const QString canonicalFilePath = file.canonicalFilePath(); - if (canonicalFilePath.isEmpty()) { +SecurityTokenPointer Sandbox::openSecurityToken(mixxx::FileInfo* pFileInfo, bool create) { + VERIFY_OR_DEBUG_ASSERT(pFileInfo) { + return nullptr; + } + const auto canonicalLocation = pFileInfo->resolveCanonicalLocation(); + if (canonicalLocation.isEmpty()) { return nullptr; } - if (file.isDir()) { - return openSecurityTokenForDir(QDir(canonicalFilePath), create); + if (pFileInfo->isDir()) { + return openSecurityTokenForDir(QDir(canonicalLocation), create); } if (sDebug) { - qDebug() << "openSecurityToken for file" << canonicalFilePath << create; + qDebug() << "openSecurityToken for file" << canonicalLocation << create; } if (!enabled()) { @@ -216,12 +238,12 @@ SecurityTokenPointer Sandbox::openSecurityToken(const QFileInfo& file, bool crea } QHash::iterator it = s_activeTokens - .find(canonicalFilePath); + .find(canonicalLocation); if (it != s_activeTokens.end()) { SecurityTokenPointer pToken(it.value()); if (pToken) { if (sDebug) { - qDebug() << "openSecurityToken QFileInfo" << canonicalFilePath + qDebug() << "openSecurityToken mixxx::FileInfo" << canonicalLocation << "using cached token for" << pToken->m_path; } return pToken; @@ -229,16 +251,17 @@ SecurityTokenPointer Sandbox::openSecurityToken(const QFileInfo& file, bool crea } // First, check for a bookmark of the key itself. - ConfigKey key = keyForCanonicalPath(canonicalFilePath); + ConfigKey key = keyForCanonicalPath(canonicalLocation); if (s_pSandboxPermissions->exists(key)) { return openTokenFromBookmark( - canonicalFilePath, + canonicalLocation, s_pSandboxPermissions->getValueString(key)); } // Next, try to open a bookmark for an existing directory but don't create a // bookmark. - SecurityTokenPointer pDirToken = openSecurityTokenForDir(file.dir(), false); + const auto parentDir = QDir(pFileInfo->canonicalLocationPath()); + SecurityTokenPointer pDirToken = openSecurityTokenForDir(parentDir, false); if (!pDirToken.isNull()) { return pDirToken; } @@ -248,20 +271,24 @@ SecurityTokenPointer Sandbox::openSecurityToken(const QFileInfo& file, bool crea } // Otherwise, try to create a token. - bool created = createSecurityToken(file); - - if (created) { - return openTokenFromBookmark( - canonicalFilePath, - s_pSandboxPermissions->getValueString(key)); + bool created = createSecurityToken(pFileInfo); + if (!created) { + return nullptr; } - return nullptr; + + return openTokenFromBookmark( + canonicalLocation, + s_pSandboxPermissions->getValueString(key)); } // static SecurityTokenPointer Sandbox::openSecurityTokenForDir(const QDir& dir, bool create) { QDir walkDir = dir; QString walkDirCanonicalPath = walkDir.canonicalPath(); + if (walkDirCanonicalPath.isEmpty()) { + // Canonical path does not exist + return SecurityTokenPointer(); + } if (sDebug) { qDebug() << "openSecurityToken for dir" << walkDirCanonicalPath << create; } diff --git a/src/util/sandbox.h b/src/util/sandbox.h index 0df170bac82..16ede7a2eb5 100644 --- a/src/util/sandbox.h +++ b/src/util/sandbox.h @@ -1,13 +1,14 @@ #pragma once -#include #include +#include #include -#include #include #include +#include #include "preferences/configobject.h" +#include "util/fileinfo.h" #ifdef Q_OS_MAC #include @@ -40,28 +41,17 @@ class Sandbox { } // Prompt the user to give us access to the path with an open-file dialog. - static bool askForAccess(const QString& canonicalPath); + static bool askForAccess(mixxx::FileInfo* pFileInfo); - static bool canAccess(const QFileInfo& info) { - SecurityTokenPointer pToken = openSecurityToken(info, true); - return info.isReadable(); - } - - static bool canAccessDir(const QDir& dir) { - SecurityTokenPointer pToken = openSecurityTokenForDir(dir, true); - QFileInfo info(dir.canonicalPath()); - return info.isReadable(); - } - - static bool createSecurityToken(const QFileInfo& info) { - return createSecurityToken(info.canonicalFilePath(), info.isDir()); - } + static bool canAccess(mixxx::FileInfo* pFileInfo); + static bool canAccessDir(const QDir& dir); + static bool createSecurityToken(mixxx::FileInfo* pFileInfo); static bool createSecurityTokenForDir(const QDir& dir) { return createSecurityToken(dir.canonicalPath(), true); } - static SecurityTokenPointer openSecurityToken(const QFileInfo& info, bool create); + static SecurityTokenPointer openSecurityToken(mixxx::FileInfo* pFileInfo, bool create); static SecurityTokenPointer openSecurityTokenForDir(const QDir& dir, bool create); private: diff --git a/src/widget/wcoverartmenu.cpp b/src/widget/wcoverartmenu.cpp index 9274561c12d..b19c0b1288c 100644 --- a/src/widget/wcoverartmenu.cpp +++ b/src/widget/wcoverartmenu.cpp @@ -5,7 +5,7 @@ #include "library/coverartutils.h" #include "moc_wcoverartmenu.cpp" -#include "util/sandbox.h" +#include "util/fileaccess.h" WCoverArtMenu::WCoverArtMenu(QWidget *parent) : QMenu(parent) { @@ -72,9 +72,7 @@ void WCoverArtMenu::slotChange() { CoverInfoRelative coverInfo; // Create a security token for the file. - QFileInfo selectedCover(selectedCoverPath); - SecurityTokenPointer pToken = Sandbox::openSecurityToken( - selectedCover, true); + auto selectedCover = mixxx::FileAccess(mixxx::FileInfo(selectedCoverPath)); QImage image(selectedCoverPath); if (image.isNull()) { // TODO(rryan): feedback diff --git a/src/widget/wlibrarysidebar.cpp b/src/widget/wlibrarysidebar.cpp index af604570658..36ec3890ead 100644 --- a/src/widget/wlibrarysidebar.cpp +++ b/src/widget/wlibrarysidebar.cpp @@ -45,9 +45,9 @@ void WLibrarySidebar::dragEnterEvent(QDragEnterEvent * event) { // drag so for now we accept all drags. Since almost every // LibraryFeature accepts all files in the drop and accepts playlist // drops we default to those flags to DragAndDropHelper. - QList files = DragAndDropHelper::supportedTracksFromUrls( + QList fileInfos = DragAndDropHelper::supportedTracksFromUrls( event->mimeData()->urls(), false, true); - if (!files.isEmpty()) { + if (!fileInfos.isEmpty()) { event->acceptProposedAction(); return; } diff --git a/src/widget/wsearchrelatedtracksmenu.cpp b/src/widget/wsearchrelatedtracksmenu.cpp index f3be9502859..19035379ebd 100644 --- a/src/widget/wsearchrelatedtracksmenu.cpp +++ b/src/widget/wsearchrelatedtracksmenu.cpp @@ -315,7 +315,7 @@ void WSearchRelatedTracksMenu::addActionsForTrack( } } { - const auto locationPath = track.getFileInfo().directory(); + const auto locationPath = track.getFileInfo().locationPath(); if (!locationPath.isEmpty()) { // Search folder and all subfolders, i.e. for "path/to/folder" // also find files in "path/to/folder/subfolder" but not in diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index b6f4ce97675..1666667a19c 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -802,7 +802,7 @@ QList WTrackMenu::getTrackRefs() const { if (m_pTrackModel) { trackRefs.reserve(m_trackIndexList.size()); for (const auto& index : m_trackIndexList) { - auto trackRef = TrackRef::fromFileInfo( + auto trackRef = TrackRef::fromFilePath( m_pTrackModel->getTrackLocation(index), m_pTrackModel->getTrackId(index)); if (!trackRef.isValid()) { @@ -816,7 +816,7 @@ QList WTrackMenu::getTrackRefs() const { for (const auto& pTrack : m_trackPointerList) { DEBUG_ASSERT(pTrack); auto trackRef = TrackRef::fromFileInfo( - pTrack->getLocation(), + pTrack->getFileInfo(), pTrack->getId()); trackRefs.push_back(std::move(trackRef)); } diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 384dcd83d32..dfc48b2f3c9 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -682,19 +682,6 @@ void WTrackTableView::dropEvent(QDropEvent * event) { // clears them) this->selectionModel()->clear(); - // Add all the dropped URLs/tracks to the track model (playlist/crate) - QList trackFiles = DragAndDropHelper::supportedTracksFromUrls( - event->mimeData()->urls(), false, true); - - QList fileLocationList; - for (const TrackFile& trackFile : trackFiles) { - fileLocationList.append(trackFile.location()); - } - - // Drag-and-drop from an external application - // eg. dragging a track from Windows Explorer onto the track table. - int numNewRows = fileLocationList.count(); - // Have to do this here because the index is invalid after // addTrack int selectionStartRow = destIndex.row(); @@ -714,11 +701,21 @@ void WTrackTableView::dropEvent(QDropEvent * event) { selectionStartRow = model()->rowCount(); } - // calling the addTracks returns number of failed additions - int tracksAdded = trackModel->addTracks(destIndex, fileLocationList); - - // Decrement # of rows to select if some were skipped - numNewRows -= (fileLocationList.size() - tracksAdded); + // Add all the dropped URLs/tracks to the track model (playlist/crate) + int numNewRows; + { + const QList trackFileInfos = + DragAndDropHelper::supportedTracksFromUrls( + event->mimeData()->urls(), false, true); + QList trackLocations; + trackLocations.reserve(trackFileInfos.size()); + for (const auto& fileInfo : trackFileInfos) { + trackLocations.append(fileInfo.location()); + } + numNewRows = trackModel->addTracks(destIndex, trackLocations); + DEBUG_ASSERT(numNewRows >= 0); + DEBUG_ASSERT(numNewRows <= trackFileInfos.size()); + } // Create the selection, but only if the track model supports // reordering. (eg. crates don't support reordering/indexes)