/* * Copyright (C) 2024 Emeric Poupon * * This file is part of LMS. * * LMS is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * LMS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with LMS. If not, see . */ #include "ScanStepAssociatePlayListImages.hpp" #include #include "core/ILogger.hpp" #include "database/IDb.hpp" #include "database/Session.hpp" #include "database/objects/Artwork.hpp" #include "database/objects/Directory.hpp" #include "database/objects/Image.hpp" #include "database/objects/PlayListFile.hpp" #include "ScanContext.hpp" namespace lms::scanner { namespace { struct PlayListArtworkAssociation { db::PlayListFileId playListFileId; db::ArtworkId artworkId; // invalid = clear }; using PlayListArtworkAssociationContainer = std::deque; db::Artwork::pointer computePreferredArtwork(db::Session& session, const db::PlayListFile::pointer& playListFile) { // Priority 1: explicit #EXTIMG: hint stored in the playlist file const std::filesystem::path& coverImageFile{ playListFile->getCoverImageFile() }; if (!coverImageFile.empty()) { const std::filesystem::path coverImagePath{ coverImageFile.is_relative() ? (playListFile->getDirectory()->getAbsolutePath() / coverImageFile).lexically_normal() : coverImageFile }; const db::Image::pointer image{ db::Image::find(session, coverImagePath) }; if (image) return db::Artwork::find(session, image->getId()); } // Priority 2: image file with the same stem as the playlist in the same directory db::Artwork::pointer artwork; db::Image::FindParameters params; params.setDirectory(playListFile->getDirectoryId()); params.setFileStem(playListFile->getAbsoluteFilePath().stem().string()); db::Image::find(session, params, [&](const db::Image::pointer& image) { if (!artwork) artwork = db::Artwork::find(session, image->getId()); }); return artwork; } void updatePlayListPreferredArtworks(db::Session& session, PlayListArtworkAssociationContainer& associations, bool forceFullBatch) { constexpr std::size_t writeBatchSize{ 50 }; while ((forceFullBatch && associations.size() >= writeBatchSize) || (!forceFullBatch && !associations.empty())) { auto transaction{ session.createWriteTransaction() }; for (std::size_t i{}; !associations.empty() && i < writeBatchSize; ++i) { const auto& assoc{ associations.front() }; db::PlayListFile::updatePreferredArtwork(session, assoc.playListFileId, assoc.artworkId); associations.pop_front(); } } } } // namespace bool ScanStepAssociatePlayListImages::needProcess(const ScanContext& context) const { return context.stats.getChangesCount() > 0; } void ScanStepAssociatePlayListImages::process(ScanContext& context) { auto& session{ _db.getTLSSession() }; { auto transaction{ session.createReadTransaction() }; context.currentStepStats.totalElems = db::PlayListFile::getCount(session); } PlayListArtworkAssociationContainer associations; db::PlayListFileId lastRetrievedId; db::IdRange idRange; while (true) { { auto transaction{ session.createReadTransaction() }; idRange = db::PlayListFile::findNextIdRange(session, lastRetrievedId, 100); lastRetrievedId = idRange.last; } if (!idRange.isValid()) break; { auto transaction{ session.createReadTransaction() }; db::PlayListFile::find(session, idRange, [&](const db::PlayListFile::pointer& playListFile) { const db::Artwork::pointer preferredArtwork{ computePreferredArtwork(session, playListFile) }; const db::ArtworkId newId{ preferredArtwork ? preferredArtwork->getId() : db::ArtworkId{} }; if (newId != playListFile->getPreferredArtworkId()) { associations.push_back({ playListFile->getId(), newId }); if (preferredArtwork) LMS_LOG(DBUPDATER, DEBUG, "Updating preferred artwork for playlist '" << playListFile->getName() << "' with image " << preferredArtwork->getAbsoluteFilePath()); else LMS_LOG(DBUPDATER, DEBUG, "Removing preferred artwork from playlist '" << playListFile->getName() << "'"); } context.currentStepStats.processedElems++; }); } updatePlayListPreferredArtworks(session, associations, true); _progressCallback(context.currentStepStats); if (_abortScan) return; } updatePlayListPreferredArtworks(session, associations, false); } } // namespace lms::scanner