diff --git a/coverdialog.cpp b/coverdialog.cpp index e246b0ba..ebdc221a 100644 --- a/coverdialog.cpp +++ b/coverdialog.cpp @@ -1,227 +1,211 @@ /** * Copyright (C) 2005 Michael Pyne * Copyright (C) 2014 Arnold Dumas * * This program 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 2 of the License, or (at your option) any later * version. * * This program 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 * this program. If not, see . */ #include "coverdialog.h" #include #include #include #include #include #include "covericonview.h" #include "covermanager.h" #include "collectionlist.h" using CoverUtility::CoverIconViewItem; class AllArtistsListViewItem : public QListWidgetItem { public: AllArtistsListViewItem(KListWidget *parent) : QListWidgetItem(i18n("<All Artists>"), parent) { } bool operator< (const QListWidgetItem& other) const { Q_UNUSED(other); return true; // Always be at the top. } }; class CaseInsensitiveItem : public QListWidgetItem { public: CaseInsensitiveItem(KListWidget *parent, const QString &text) : QListWidgetItem(text, parent) { } bool operator< (const QListWidgetItem& other) const { return text().toLower().localeAwareCompare(other.text().toLower()); } }; CoverDialog::CoverDialog(QWidget *parent) : QWidget(parent, Qt::Dialog) { setupUi(this); setObjectName( QLatin1String("juk_cover_dialog" )); m_searchLine->setClearButtonShown(true); connect(m_artists, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(slotArtistClicked(QListWidgetItem*))); connect(m_covers, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotContextRequested(QPoint))); connect(m_searchLine, SIGNAL(textChanged(QString)), this, SLOT(slotSearchPatternChanged(QString))); } CoverDialog::~CoverDialog() { } void CoverDialog::show() { m_artists->clear(); m_covers->clear(); QStringList artists = CollectionList::instance()->uniqueSet(CollectionList::Artists); new AllArtistsListViewItem(m_artists); for(QStringList::ConstIterator it = artists.constBegin(); it != artists.constEnd(); ++it) new CaseInsensitiveItem(m_artists, *it); QTimer::singleShot(0, this, SLOT(loadCovers())); QWidget::show(); } -// Here we try to keep the GUI from freezing for too long while we load the -// covers. +// TODO: Make this concurrent on a non-GUI thread void CoverDialog::loadCovers() { - CoverDataMapIterator it, end; - int i = 0; - - it = CoverManager::begin(); - end = CoverManager::end(); + auto it = CoverManager::begin(); + const auto &end = CoverManager::end(); for(; it != end; ++it) { - (void) new CoverIconViewItem(it.key(), m_covers); - - // TODO: Threading! - if(++i == 10) { - i = 0; - kapp->processEvents(); - } + (void) new CoverIconViewItem(it->first, m_covers); } } // TODO: Add a way to show cover art for tracks with no artist. void CoverDialog::slotArtistClicked(QListWidgetItem *item) { m_covers->clear(); if (!item) { return; } if(dynamic_cast(item)) { // All artists. loadCovers(); } else { QString artist = item->text().toLower(); CoverDataMapIterator it, end; it = CoverManager::begin(); end = CoverManager::end(); for(; it != end; ++it) { - if(it.value()->artist == artist) - (void) new CoverIconViewItem(it.key(), m_covers); + if(it->second.artist == artist) + (void) new CoverIconViewItem(it->first, m_covers); } } } void CoverDialog::slotContextRequested(const QPoint &pt) { static KMenu *menu = 0; QListWidgetItem* item = m_covers->currentItem(); if(!item) return; if(!menu) { menu = new KMenu(this); menu->addAction(i18n("Remove Cover"), this, SLOT(removeSelectedCover())); } QPoint globalPt = m_covers->mapToGlobal(pt); menu->popup(globalPt); } void CoverDialog::slotSearchPatternChanged(const QString& pattern) { m_covers->clear(); QListWidgetItem* item = m_artists->currentItem(); // If the expression is cleared, then use slotArtistClicked. if (pattern.isEmpty()) { slotArtistClicked(item); } else { QRegExp filter(pattern, Qt::CaseInsensitive, QRegExp::Wildcard); QString artist = item->text().toLower(); - CoverDataMapIterator it, end; - - it = CoverManager::begin(); - end = CoverManager::end(); + CoverDataMapIterator it = CoverManager::begin(); + const CoverDataMapIterator end = CoverManager::end(); // Here, only show cover that match the search pattern. if (dynamic_cast(item)) { - for(; it != end; ++it) { - if (filter.indexIn(it.value()->artist) != -1) { - - (void) new CoverIconViewItem(it.key(), m_covers); + if (filter.indexIn(it->second.artist) != -1) { + (void) new CoverIconViewItem(it->first, m_covers); } } } - // Here, only show the covers that match the search pattern and // that have the same artist as the currently selected one. else { - for(; it != end; ++it) { - if (it.value()->artist == artist - && ((filter.indexIn(it.value()->artist) != -1) - || (filter.indexIn(it.value()->album) != -1))) { - - (void) new CoverIconViewItem(it.key(), m_covers); + if (it->second.artist == artist + && ((filter.indexIn(it->second.artist) != -1) + || (filter.indexIn(it->second.album) != -1))) + { + (void) new CoverIconViewItem(it->first, m_covers); } } } } } void CoverDialog::removeSelectedCover() { CoverIconViewItem *coverItem = m_covers->currentItem(); if(!coverItem || !coverItem->isSelected()) { kWarning() << "No item selected for removeSelectedCover.\n"; return; } if(!CoverManager::removeCover(coverItem->id())) kError() << "Unable to remove selected cover: " << coverItem->id() << endl; else delete coverItem; } // vim: set et sw=4 tw=0 sta: diff --git a/covericonview.cpp b/covericonview.cpp index 8a6def60..beb762f8 100644 --- a/covericonview.cpp +++ b/covericonview.cpp @@ -1,60 +1,47 @@ /** * Copyright (C) 2005 Michael Pyne * Copyright (C) 2014 Arnold Dumas * * This program 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 2 of the License, or (at your option) any later * version. * * This program 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 * this program. If not, see . */ #include "covericonview.h" #include "covermanager.h" using CoverUtility::CoverIconViewItem; CoverIconViewItem::CoverIconViewItem(coverKey id, KListWidget *parent) : QListWidgetItem(parent), m_id(id) { - CoverDataPtr data = CoverManager::coverInfo(id); - setText(QString("%1 - %2").arg(data->artist, data->album)); - setIcon(data->thumbnail()); + const auto &data = CoverManager::coverInfo(id); + setText(QString("%1 - %2").arg(data.artist, data.album)); + setIcon(data.thumbnail()); setSizeHint(QSize(140, 150)); } CoverIconView::CoverIconView(QWidget *parent, const char *name) : KListWidget(parent) { setObjectName(QLatin1String(name)); setResizeMode(KListWidget::Adjust); setViewMode(KListWidget::IconMode); setIconSize(QSize(130, 140)); setMovement(KListWidget::Static); setContextMenuPolicy(Qt::CustomContextMenu); } CoverIconViewItem *CoverIconView::currentItem() const { return static_cast(KListWidget::currentItem()); } -// TODO: port to Qt4 -#if 0 -Q3DragObject *CoverIconView::dragObject() -{ - // Temporarily disabled pending conversion of the cover manager icon view - // to Qt 4 ish stuff. - CoverIconViewItem *item = currentItem(); - if(item) - return new CoverDrag(item->id(), this); - return 0; -} -#endif - // vim: set et sw=4 tw=0 sta: diff --git a/coverinfo.cpp b/coverinfo.cpp index 47d0638b..e4180f8c 100644 --- a/coverinfo.cpp +++ b/coverinfo.cpp @@ -1,484 +1,484 @@ /** * Copyright (C) 2004 Nathan Toone * Copyright (C) 2005, 2008 Michael Pyne * * This program 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 2 of the License, or (at your option) any later * version. * * This program 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 * this program. If not, see . */ #include "coverinfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef TAGLIB_WITH_MP4 #include #include #include #include #endif #include "mediafiles.h" #include "collectionlist.h" #include "playlistsearch.h" #include "playlistitem.h" #include "tag.h" struct CoverPopup : public QWidget { CoverPopup(const QPixmap &image, const QPoint &p) : QWidget(0, Qt::WindowFlags(Qt::WA_DeleteOnClose | Qt::X11BypassWindowManagerHint)) { QHBoxLayout *layout = new QHBoxLayout(this); QLabel *label = new QLabel(this); layout->addWidget(label); label->setFrameStyle(QFrame::Box | QFrame::Raised); label->setLineWidth(1); label->setPixmap(image); setGeometry(p.x(), p.y(), label->width(), label->height()); show(); } virtual void leaveEvent(QEvent *) { close(); } virtual void mouseReleaseEvent(QMouseEvent *) { close(); } }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// CoverInfo::CoverInfo(const FileHandle &file) : m_file(file), m_hasCover(false), m_hasAttachedCover(false), m_haveCheckedForCover(false), m_coverKey(CoverManager::NoMatch) { } bool CoverInfo::hasCover() const { if(m_haveCheckedForCover) return m_hasCover || m_hasAttachedCover; m_haveCheckedForCover = true; // Check for new-style covers. First let's determine what our coverKey is // if it's not already set, as that's also tracked by the CoverManager. if(m_coverKey == CoverManager::NoMatch) m_coverKey = CoverManager::idForTrack(m_file.absFilePath()); // We were assigned a key, let's see if we already have a cover. Notice // that due to the way the CoverManager is structured, we should have a // cover if we have a cover key. If we don't then either there's a logic // error, or the user has been mucking around where they shouldn't. if(m_coverKey != CoverManager::NoMatch) m_hasCover = CoverManager::hasCover(m_coverKey); // Check if it's embedded in the file itself. m_hasAttachedCover = hasEmbeddedAlbumArt(); if(m_hasAttachedCover) return true; // Look for cover.jpg or cover.png in the directory. if(QFile::exists(m_file.fileInfo().absolutePath() + "/cover.jpg") || QFile::exists(m_file.fileInfo().absolutePath() + "/cover.png")) { m_hasCover = true; } return m_hasCover; } void CoverInfo::clearCover() { m_hasCover = false; m_hasAttachedCover = false; // Re-search for cover since we may still have a different type of cover. m_haveCheckedForCover = false; // We don't need to call removeCover because the CoverManager will // automatically unlink the cover if we were the last track to use it. CoverManager::setIdForTrack(m_file.absFilePath(), CoverManager::NoMatch); m_coverKey = CoverManager::NoMatch; } void CoverInfo::setCover(const QImage &image) { if(image.isNull()) return; m_haveCheckedForCover = true; m_hasCover = true; QPixmap cover = QPixmap::fromImage(image); // If we use replaceCover we'll change the cover for every other track // with the same coverKey, which we don't want since that case will be // handled by Playlist. Instead just replace this track's cover. m_coverKey = CoverManager::addCover(cover, m_file.tag()->artist(), m_file.tag()->album()); if(m_coverKey != CoverManager::NoMatch) CoverManager::setIdForTrack(m_file.absFilePath(), m_coverKey); } void CoverInfo::setCoverId(coverKey id) { m_coverKey = id; m_haveCheckedForCover = true; m_hasCover = id != CoverManager::NoMatch; // Inform CoverManager of the change. CoverManager::setIdForTrack(m_file.absFilePath(), m_coverKey); } void CoverInfo::applyCoverToWholeAlbum(bool overwriteExistingCovers) const { QString artist = m_file.tag()->artist(); QString album = m_file.tag()->album(); PlaylistSearch::ComponentList components; ColumnList columns; columns.append(PlaylistItem::ArtistColumn); components.append(PlaylistSearch::Component(artist, false, columns, PlaylistSearch::Component::Exact)); columns.clear(); columns.append(PlaylistItem::AlbumColumn); components.append(PlaylistSearch::Component(album, false, columns, PlaylistSearch::Component::Exact)); PlaylistList playlists; playlists.append(CollectionList::instance()); PlaylistSearch search(playlists, components, PlaylistSearch::MatchAll); // Search done, iterate through results. PlaylistItemList results = search.matchedItems(); PlaylistItemList::ConstIterator it = results.constBegin(); for(; it != results.constEnd(); ++it) { // Don't worry about files that somehow already have a tag, // unless the conversion is forced. if(!overwriteExistingCovers && (*it)->file().coverInfo()->coverId() != CoverManager::NoMatch) continue; (*it)->file().coverInfo()->setCoverId(m_coverKey); } } coverKey CoverInfo::coverId() const { if(m_coverKey == CoverManager::NoMatch) m_coverKey = CoverManager::idForTrack(m_file.absFilePath()); return m_coverKey; } QPixmap CoverInfo::pixmap(CoverSize size) const { if(hasCover() && m_coverKey != CoverManager::NoMatch) { return CoverManager::coverFromId(m_coverKey, size == Thumbnail ? CoverManager::Thumbnail : CoverManager::FullSize); } QImage cover; // If m_hasCover is still true we must have a directory cover image. if(m_hasCover) { QString fileName = m_file.fileInfo().absolutePath() + "/cover.jpg"; if(!cover.load(fileName)) { fileName = m_file.fileInfo().absolutePath() + "/cover.png"; if(!cover.load(fileName)) return QPixmap(); } } // If we get here, see if there is an embedded cover. cover = embeddedAlbumArt(); if(!cover.isNull() && size == Thumbnail) cover = scaleCoverToThumbnail(cover); if(cover.isNull()) { return QPixmap(); } return QPixmap::fromImage(cover); } QString CoverInfo::localPathToCover(const QString &fallbackFileName) const { if(m_coverKey != CoverManager::NoMatch) { - QString path = CoverManager::coverInfo(m_coverKey)->path; + QString path = CoverManager::coverInfo(m_coverKey).path; if(!path.isEmpty()) return path; } if(hasEmbeddedAlbumArt()) { QFile albumArtFile(fallbackFileName); if(!albumArtFile.open(QIODevice::ReadWrite)) { return QString(); } QImage albumArt = embeddedAlbumArt(); albumArt.save(&albumArtFile, "PNG"); return fallbackFileName; } QString basePath = m_file.fileInfo().absolutePath(); if(QFile::exists(basePath + "/cover.jpg")) return basePath + "/cover.jpg"; else if(QFile::exists(basePath + "/cover.png")) return basePath + "/cover.png"; return QString(); } bool CoverInfo::hasEmbeddedAlbumArt() const { QScopedPointer fileTag( MediaFiles::fileFactoryByType(m_file.absFilePath())); if (TagLib::MPEG::File *mpegFile = dynamic_cast(fileTag.data())) { TagLib::ID3v2::Tag *id3tag = mpegFile->ID3v2Tag(false); if (!id3tag) { kError() << m_file.absFilePath() << "seems to have invalid ID3 tag"; return false; } // Look for attached picture frames. TagLib::ID3v2::FrameList frames = id3tag->frameListMap()["APIC"]; return !frames.isEmpty(); } else if (TagLib::FLAC::File *flacFile = dynamic_cast(fileTag.data())) { // Look if images are embedded. return !flacFile->pictureList().isEmpty(); } #ifdef TAGLIB_WITH_MP4 else if(TagLib::MP4::File *mp4File = dynamic_cast(fileTag.data())) { TagLib::MP4::Tag *tag = mp4File->tag(); if (tag) { TagLib::MP4::ItemListMap &items = tag->itemListMap(); return items.contains("covr"); } } #endif return false; } static QImage embeddedMPEGAlbumArt(TagLib::ID3v2::Tag *id3tag) { if(!id3tag) return QImage(); // Look for attached picture frames. TagLib::ID3v2::FrameList frames = id3tag->frameListMap()["APIC"]; if(frames.isEmpty()) return QImage(); // According to the spec attached picture frames have different types. // So we should look for the corresponding picture depending on what // type of image (i.e. front cover, file info) we want. If only 1 // frame, just return that (scaled if necessary). TagLib::ID3v2::AttachedPictureFrame *selectedFrame = 0; if(frames.size() != 1) { TagLib::ID3v2::FrameList::Iterator it = frames.begin(); for(; it != frames.end(); ++it) { // This must be dynamic_cast<>, TagLib will return UnknownFrame in APIC for // encrypted frames. TagLib::ID3v2::AttachedPictureFrame *frame = dynamic_cast(*it); // Both thumbnail and full size should use FrontCover, as // FileIcon may be too small even for thumbnail. if(frame && frame->type() != TagLib::ID3v2::AttachedPictureFrame::FrontCover) continue; selectedFrame = frame; break; } } // If we get here we failed to pick a picture, or there was only one, // so just use the first picture. if(!selectedFrame) selectedFrame = dynamic_cast(frames.front()); if(!selectedFrame) // Could occur for encrypted picture frames. return QImage(); TagLib::ByteVector picture = selectedFrame->picture(); return QImage::fromData( reinterpret_cast(picture.data()), picture.size()); } static QImage embeddedFLACAlbumArt(TagLib::FLAC::File *flacFile) { TagLib::List flacPictures = flacFile->pictureList(); if(flacPictures.isEmpty()) { // No pictures are embedded. return QImage(); } // Always use first picture - even if multiple are embedded. TagLib::ByteVector coverData = flacPictures[0]->data(); QImage result = QImage::fromData( reinterpret_cast(coverData.data()), coverData.size()); if(!result.isNull()) { return result; } // Error while casting image. return QImage(); } #ifdef TAGLIB_WITH_MP4 static QImage embeddedMP4AlbumArt(TagLib::MP4::Tag *tag) { TagLib::MP4::ItemListMap &items = tag->itemListMap(); if(!items.contains("covr")) return QImage(); TagLib::MP4::CoverArtList covers = items["covr"].toCoverArtList(); TagLib::MP4::CoverArtList::ConstIterator end = covers.end(); for(TagLib::MP4::CoverArtList::ConstIterator it = covers.begin(); it != end; ++it) { TagLib::MP4::CoverArt cover = *it; TagLib::ByteVector coverData = cover.data(); QImage result = QImage::fromData( reinterpret_cast(coverData.data()), coverData.size()); if(!result.isNull()) return result; } // No appropriate image found return QImage(); } #endif void CoverInfo::popup() const { QPixmap image = pixmap(FullSize); QPoint mouse = QCursor::pos(); QRect desktop = QApplication::desktop()->screenGeometry(mouse); int x = mouse.x(); int y = mouse.y(); int height = image.size().height() + 4; int width = image.size().width() + 4; // Detect the right direction to pop up (always towards the center of the // screen), try to pop up with the mouse pointer 10 pixels into the image in // both directions. If we're too close to the screen border for this margin, // show it at the screen edge, accounting for the four pixels (two on each // side) for the window border. if(x - desktop.x() < desktop.width() / 2) x = (x - desktop.x() < 10) ? desktop.x() : (x - 10); else x = (x - desktop.x() > desktop.width() - 10) ? desktop.width() - width +desktop.x() : (x - width + 10); if(y - desktop.y() < desktop.height() / 2) y = (y - desktop.y() < 10) ? desktop.y() : (y - 10); else y = (y - desktop.y() > desktop.height() - 10) ? desktop.height() - height + desktop.y() : (y - height + 10); new CoverPopup(image, QPoint(x, y)); } QImage CoverInfo::embeddedAlbumArt() const { QScopedPointer fileTag( MediaFiles::fileFactoryByType(m_file.absFilePath())); if (TagLib::MPEG::File *mpegFile = dynamic_cast(fileTag.data())) { TagLib::ID3v2::Tag *id3tag = mpegFile->ID3v2Tag(false); return embeddedMPEGAlbumArt(id3tag); } else if (TagLib::FLAC::File *flacFile = dynamic_cast(fileTag.data())) { return embeddedFLACAlbumArt(flacFile); } #ifdef TAGLIB_WITH_MP4 else if(TagLib::MP4::File *mp4File = dynamic_cast(fileTag.data())) { TagLib::MP4::Tag *tag = mp4File->tag(); if (tag) { return embeddedMP4AlbumArt(tag); } } #endif return QImage(); } QImage CoverInfo::scaleCoverToThumbnail(const QImage &image) const { return image.scaled(80, 80, Qt::KeepAspectRatio, Qt::SmoothTransformation); } // vim: set et sw=4 tw=0 sta: diff --git a/covermanager.cpp b/covermanager.cpp index caa57c7b..8c8a953e 100644 --- a/covermanager.cpp +++ b/covermanager.cpp @@ -1,667 +1,657 @@ /** * Copyright (C) 2005, 2008 Michael Pyne * * This program 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 2 of the License, or (at your option) any later * version. * * This program 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 * this program. If not, see . */ #include "covermanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "juk.h" #include "coverproxy.h" // This is a dictionary to map the track path to their ID. Otherwise we'd have // to store this info with each CollectionListItem, which would break the cache // of users who upgrade, and would just generally be a big mess. typedef QHash TrackLookupMap; static const char dragMimetype[] = "application/x-juk-coverid"; const coverKey CoverManager::NoMatch = 0; // Used to save and load CoverData from a QDataStream QDataStream &operator<<(QDataStream &out, const CoverData &data); QDataStream &operator>>(QDataStream &in, CoverData &data); // // Implementation of CoverSaveHelper class // CoverSaveHelper::CoverSaveHelper(QObject *parent) : QObject(parent), m_timer(new QTimer(this)) { connect(m_timer, SIGNAL(timeout()), SLOT(commitChanges())); // Wait 5 seconds before committing to avoid lots of disk activity for // rapid changes. m_timer->setSingleShot(true); m_timer->setInterval(5000); } void CoverSaveHelper::saveCovers() { m_timer->start(); // Restarts if already triggered. } void CoverSaveHelper::commitChanges() { CoverManager::saveCovers(); } // // Implementation of CoverData struct // QPixmap CoverData::pixmap() const { return CoverManager::coverFromData(*this, CoverManager::FullSize); } QPixmap CoverData::thumbnail() const { return CoverManager::coverFromData(*this, CoverManager::Thumbnail); } /** * This class is responsible for actually keeping track of the storage for the * different covers and such. It holds the covers, and the map of path names * to cover ids, and has a few utility methods to load and save the data. * * @author Michael Pyne * @see CoverManager */ class CoverManagerPrivate { public: - /// Maps coverKey id's to CoverDataPtrs + /// Maps coverKey id's to CoverData CoverDataMap covers; /// Maps file names to coverKey id's. TrackLookupMap tracks; /// A map of outstanding download KJobs to their coverKey QMap downloadJobs; /// A static pixmap cache is maintained for covers, with key format of: /// 'f' followed by the pathname for FullSize covers, and /// 't' followed by the pathname for Thumbnail covers. /// However only thumbnails are currently cached. CoverManagerPrivate() : m_timer(new CoverSaveHelper(0)), m_coverProxy(0) { loadCovers(); } ~CoverManagerPrivate() { delete m_timer; delete m_coverProxy; saveCovers(); } void requestSave() { m_timer->saveCovers(); } /** * Creates the data directory for the covers if it doesn't already exist. * Must be in this class for loadCovers() and saveCovers(). */ void createDataDir() const; /** * Returns the next available unused coverKey that can be used for * inserting new items. * * @return unused id that can be used for new CoverData */ coverKey nextId() const; void saveCovers() const; CoverProxy *coverProxy() { if(!m_coverProxy) m_coverProxy = new CoverProxy; return m_coverProxy; } private: void loadCovers(); /** * @return the full path and filename of the file storing the cover * lookup map and the translations between pathnames and ids. */ QString coverLocation() const; CoverSaveHelper *m_timer; CoverProxy *m_coverProxy; }; // This is responsible for making sure that the CoverManagerPrivate class // gets properly destructed on shutdown. K_GLOBAL_STATIC(CoverManagerPrivate, sd) // // Implementation of CoverManagerPrivate methods. // void CoverManagerPrivate::createDataDir() const { QDir dir; QString dirPath(QDir::cleanPath(coverLocation() + "/..")); if(!dir.exists(dirPath)) KStandardDirs::makeDir(dirPath); } void CoverManagerPrivate::saveCovers() const { // Make sure the directory exists first. createDataDir(); QFile file(coverLocation()); kDebug() << "Opening covers db: " << coverLocation(); if(!file.open(QIODevice::WriteOnly)) { kError() << "Unable to save covers to disk!\n"; return; } QDataStream out(&file); // Write out the version and count - out << quint32(0) << quint32(covers.count()); + out << quint32(0) << quint32(covers.size()); - kDebug() << "Writing out" << covers.count() << "covers."; + kDebug() << "Writing out" << covers.size() << "covers."; // Write out the data - for(CoverDataMap::const_iterator it = covers.begin(); it != covers.end(); ++it) { - out << quint32(it.key()); - out << *it.value(); + for(const auto &it : covers) { + out << quint32(it.first); + out << it.second; } // Now write out the track mapping. out << quint32(tracks.count()); kDebug() << "Writing out" << tracks.count() << "tracks."; TrackLookupMap::ConstIterator trackMapIt = tracks.constBegin(); while(trackMapIt != tracks.constEnd()) { out << trackMapIt.key() << quint32(trackMapIt.value()); ++trackMapIt; } } void CoverManagerPrivate::loadCovers() { QFile file(coverLocation()); if(!file.open(QIODevice::ReadOnly)) { // Guess we don't have any covers yet. return; } QDataStream in(&file); quint32 count, version; // First thing we'll read in will be the version. // Only version 0 is defined for now. in >> version; if(version > 0) { kError() << "Cover database was created by a higher version of JuK,\n"; kError() << "I don't know what to do with it.\n"; return; } // Read in the count next, then the data. in >> count; kDebug() << "Loading" << count << "covers."; for(quint32 i = 0; i < count; ++i) { // Read the id, and 3 QStrings for every 1 of the count. quint32 id; - CoverDataPtr data(new CoverData); + CoverData data; in >> id; - in >> *data; - data->refCount = 0; + in >> data; + data.refCount = 0; covers[(coverKey) id] = data; } in >> count; kDebug() << "Loading" << count << "tracks"; for(quint32 i = 0; i < count; ++i) { QString path; quint32 id; in >> path >> id; // If we somehow already managed to load a cover id with this path, // don't do so again. Possible due to a coding error during 3.5 // development. if(KDE_ISLIKELY(!tracks.contains(path))) { - ++covers[(coverKey) id]->refCount; // Another track using this. + ++covers[(coverKey) id].refCount; // Another track using this. tracks.insert(path, id); } } kDebug() << "Tracks hash table has" << tracks.size() << "entries."; } QString CoverManagerPrivate::coverLocation() const { return KGlobal::dirs()->saveLocation("appdata") + "coverdb/covers"; } -// XXX: This could probably use some improvement, I don't like the linear -// search for ID idea. Linear search is used instead of covers.size() since we want to -// re-use old IDs if possible. coverKey CoverManagerPrivate::nextId() const { // Start from 1... coverKey key = 1; - while(covers.contains(key)) + while(covers.find(key) != covers.end()) ++key; return key; } // // Implementation of CoverDrag // CoverDrag::CoverDrag(coverKey id) : QMimeData() { QPixmap cover = CoverManager::coverFromId(id); setImageData(cover.toImage()); setData(dragMimetype, QByteArray::number(qulonglong(id), 10)); } bool CoverDrag::isCover(const QMimeData *data) { return data->hasImage() || data->hasFormat(dragMimetype); } coverKey CoverDrag::idFromData(const QMimeData *data) { bool ok = false; if(!data->hasFormat(dragMimetype)) return CoverManager::NoMatch; coverKey id = data->data(dragMimetype).toULong(&ok); if(!ok) return CoverManager::NoMatch; return id; } const char *CoverDrag::mimetype() { return dragMimetype; } // // Implementation of CoverManager methods. // coverKey CoverManager::idFromMetadata(const QString &artist, const QString &album) { - // Search for the string, yay! It might make sense to use a cache here, - // if so it's not hard to add a QCache. - CoverDataMap::const_iterator it = begin(); - CoverDataMap::const_iterator endIt = end(); + CoverDataMap::const_iterator it = begin(); + const CoverDataMap::const_iterator endIt = end(); for(; it != endIt; ++it) { - if(it.value()->album == album.toLower() && it.value()->artist == artist.toLower()) - return it.key(); + if(it->second.album == album.toLower() && it->second.artist == artist.toLower()) + return it->first; } return NoMatch; } QPixmap CoverManager::coverFromId(coverKey id, Size size) { - CoverDataPtr info = coverInfo(id); - - if(!info) + const auto &info = data()->covers.find(id); + if(info == data()->covers.end()) return QPixmap(); if(size == Thumbnail) - return info->thumbnail(); + return info->second.thumbnail(); - return info->pixmap(); + return info->second.pixmap(); } QPixmap CoverManager::coverFromData(const CoverData &coverData, Size size) { QString path = coverData.path; // Prepend a tag to the path to separate in the cache between full size // and thumbnail pixmaps. If we add a different kind of pixmap in the // future we also need to add a tag letter for it. if(size == FullSize) path.prepend('f'); else path.prepend('t'); // Check in cache for the pixmap. QPixmap pix; if(QPixmapCache::find(path, pix)) return pix; // Not in cache, load it and add it. if(!pix.load(coverData.path)) return QPixmap(); // Only thumbnails are cached to avoid depleting global cache. Caching // full size pics is not really useful as they are infrequently shown. if(size == Thumbnail) { // Double scale is faster and 99% as accurate QSize newSize(pix.size()); newSize.scale(80, 80, Qt::KeepAspectRatio); pix = pix.scaled(2 * newSize) .scaled(newSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); QPixmapCache::insert(path, pix); } return pix; } coverKey CoverManager::addCover(const QPixmap &large, const QString &artist, const QString &album) { kDebug() << "Adding new pixmap to cover database.\n"; if(large.isNull()) { kDebug() << "The pixmap you're trying to add is NULL!\n"; return NoMatch; } KTemporaryFile tempFile; if(!tempFile.open()) { kError() << "Unable to open file for pixmap cover, unable to add cover to DB\n"; return NoMatch; } // Now that file is open, file name will be available, which is where we want // to save the pixmap as a .png. if(!large.save(tempFile.fileName(), "PNG")) { kError() << "Unable to save pixmap to " << tempFile.fileName() << endl; return NoMatch; } return addCover(KUrl::fromPath(tempFile.fileName()), artist, album); } coverKey CoverManager::addCover(const KUrl &path, const QString &artist, const QString &album) { coverKey id = data()->nextId(); - CoverDataPtr coverData(new CoverData); + CoverData coverData; QString fileNameExt = path.fileName(); int extPos = fileNameExt.lastIndexOf('.'); fileNameExt = fileNameExt.mid(extPos); if(extPos == -1) fileNameExt = ""; // Copy it to a local file first. QString ext = QString("/coverdb/coverID-%1%2").arg(id).arg(fileNameExt); - coverData->path = KGlobal::dirs()->saveLocation("appdata") + ext; + coverData.path = KGlobal::dirs()->saveLocation("appdata") + ext; - kDebug() << "Saving pixmap to " << coverData->path; + kDebug() << "Saving pixmap to " << coverData.path; data()->createDataDir(); - coverData->artist = artist.toLower(); - coverData->album = album.toLower(); - coverData->refCount = 0; + coverData.artist = artist.toLower(); + coverData.album = album.toLower(); + coverData.refCount = 0; - data()->covers[id] = coverData; + data()->covers.emplace(id, coverData); // Can't use NetAccess::download() since if path is already a local file // (which is possible) then that function will return without copying, since // it assumes we merely want the file on the hard disk somewhere. KIO::FileCopyJob *job = KIO::file_copy( - path, KUrl::fromPath(coverData->path), + path, KUrl::fromPath(coverData.path), -1 /* perms */,KIO::HideProgressInfo | KIO::Overwrite ); QObject::connect(job, SIGNAL(result(KJob*)), data()->coverProxy(), SLOT(handleResult(KJob*))); data()->downloadJobs.insert(job, id); job->start(); data()->requestSave(); // Save changes when possible. return id; } /** * This is called when our cover downloader has completed. Typically there * should be no issues so we just need to ensure that the newly downloaded * cover is picked up by invalidating any cache entry for it. If it didn't * download successfully we're in kind of a pickle as we've already assigned * a coverKey, which we need to go and erase. */ void CoverManager::jobComplete(KJob *job, bool completedSatisfactory) { coverKey id = NoMatch; if(data()->downloadJobs.contains(job)) id = data()->downloadJobs[job]; if(id == NoMatch) { kError() << "No information on what download job" << job << "is."; data()->downloadJobs.remove(job); return; } if(!completedSatisfactory) { kError() << "Job" << job << "failed, but not handled yet."; removeCover(id); data()->downloadJobs.remove(job); JuK::JuKInstance()->coverDownloaded(QPixmap()); return; } - CoverDataPtr coverData = data()->covers[id]; + CoverData coverData = data()->covers[id]; // Make sure the new cover isn't inadvertently cached. - QPixmapCache::remove(QString("f%1").arg(coverData->path)); - QPixmapCache::remove(QString("t%1").arg(coverData->path)); + QPixmapCache::remove(QString("f%1").arg(coverData.path)); + QPixmapCache::remove(QString("t%1").arg(coverData.path)); - JuK::JuKInstance()->coverDownloaded(coverFromData(*coverData, CoverManager::Thumbnail)); + JuK::JuKInstance()->coverDownloaded(coverFromData(coverData, CoverManager::Thumbnail)); } bool CoverManager::hasCover(coverKey id) { - return data()->covers.contains(id); + return data()->covers.find(id) != data()->covers.end(); } bool CoverManager::removeCover(coverKey id) { if(!hasCover(id)) return false; // Remove cover from cache. - CoverDataPtr coverData = coverInfo(id); - QPixmapCache::remove(QString("f%1").arg(coverData->path)); - QPixmapCache::remove(QString("t%1").arg(coverData->path)); + CoverData coverData = coverInfo(id); + QPixmapCache::remove(QString("f%1").arg(coverData.path)); + QPixmapCache::remove(QString("t%1").arg(coverData.path)); // Remove references to files that had that track ID. QList affectedFiles = data()->tracks.keys(id); foreach (const QString &file, affectedFiles) { data()->tracks.remove(file); } // Remove covers from disk. - QFile::remove(coverData->path); + QFile::remove(coverData.path); // Finally, forget that we ever knew about this cover. - data()->covers.remove(id); + data()->covers.erase(id); data()->requestSave(); return true; } bool CoverManager::replaceCover(coverKey id, const QPixmap &large) { if(!hasCover(id)) return false; - CoverDataPtr coverData = coverInfo(id); + CoverData coverData = coverInfo(id); // Empty old pixmaps from cache. - QPixmapCache::remove(QString("t%1").arg(coverData->path)); - QPixmapCache::remove(QString("f%1").arg(coverData->path)); + QPixmapCache::remove(QString("t%1").arg(coverData.path)); + QPixmapCache::remove(QString("f%1").arg(coverData.path)); - large.save(coverData->path, "PNG"); + large.save(coverData.path, "PNG"); // No save is needed, as all that has changed is the on-disk cover data, // not the list of tracks or covers. return true; } CoverManagerPrivate *CoverManager::data() { return sd; } void CoverManager::saveCovers() { data()->saveCovers(); } void CoverManager::shutdown() { sd.destroy(); } CoverDataMapIterator CoverManager::begin() { - return data()->covers.constBegin(); + return data()->covers.begin(); } CoverDataMapIterator CoverManager::end() { - return data()->covers.constEnd(); -} - -CoverList CoverManager::keys() -{ - return data()->covers.keys(); + return data()->covers.end(); } void CoverManager::setIdForTrack(const QString &path, coverKey id) { coverKey oldId = data()->tracks.value(path, NoMatch); if(data()->tracks.contains(path) && (id == oldId)) return; // We're already done. if(oldId != NoMatch) { - data()->covers[oldId]->refCount--; + data()->covers[oldId].refCount--; data()->tracks.remove(path); - if(data()->covers[oldId]->refCount == 0) { + if(data()->covers[oldId].refCount == 0) { kDebug() << "Cover " << oldId << " is unused, removing.\n"; removeCover(oldId); } } if(id != NoMatch) { - data()->covers[id]->refCount++; + data()->covers[id].refCount++; data()->tracks.insert(path, id); } data()->requestSave(); } coverKey CoverManager::idForTrack(const QString &path) { return data()->tracks.value(path, NoMatch); } -CoverDataPtr CoverManager::coverInfo(coverKey id) +CoverData CoverManager::coverInfo(coverKey id) { - if(data()->covers.contains(id)) + if(hasCover(id)) return data()->covers[id]; - return CoverDataPtr(0); + // TODO throw new something or other + return CoverData{}; } /** * Write @p data out to @p out. * * @param out the data stream to write @p data out to. * @param data the CoverData to write out. * @return the data stream that the data was written to. */ QDataStream &operator<<(QDataStream &out, const CoverData &data) { out << data.artist; out << data.album; out << data.path; return out; } /** * Read @p data from @p in. * * @param in the data stream to read from. * @param data the CoverData to read into. * @return the data stream read from. */ QDataStream &operator>>(QDataStream &in, CoverData &data) { in >> data.artist; in >> data.album; in >> data.path; return in; } // vim: set et sw=4 tw=0 sta: diff --git a/covermanager.h b/covermanager.h index fcbef1a6..b73db68b 100644 --- a/covermanager.h +++ b/covermanager.h @@ -1,294 +1,284 @@ /** * Copyright (C) 2005, 2008 Michael Pyne * * This program 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 2 of the License, or (at your option) any later * version. * * This program 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 * this program. If not, see . */ #ifndef JUK_COVERMANAGER_H #define JUK_COVERMANAGER_H #include #include #include #include +#include + class CoverManagerPrivate; class CoverProxy; class QPixmap; class QTimer; class KJob; -template -class QMap; - template class QList; class KUrl; /** * This class saves the covers when its saveCovers() slot is called to avoid * making CoverManager a QObject and avoid moving the actual implementation * class (CoverManagerPrivate) to this .h file. Used with a QTimer to save * the covers after changes are made. */ class CoverSaveHelper : public QObject { Q_OBJECT public: CoverSaveHelper(QObject *parent); void saveCovers(); private slots: void commitChanges(); private: QTimer *m_timer; }; /** * This class holds the data on a cover. This includes the path to the cover * representation on-disk, and the artist and album associated with the cover. * Don't assume that the artist or album information is filled out, it is * there to allow the CoverManager to try to automatically assign covers to * new tracks. * * @author Michael Pyne * @see CoverManager */ -class CoverData : public KShared +class CoverData { public: QPixmap pixmap() const; QPixmap thumbnail() const; QString artist; QString album; QString path; unsigned refCount; // Refers to number of tracks using this. }; -typedef KSharedPtr CoverDataPtr; typedef unsigned long coverKey; ///< Type of the id for a cover. -typedef QMap CoverDataMap; - -// I can't believe this actually works... -typedef CoverDataMap::const_iterator CoverDataMapIterator; +using CoverDataMap = std::map; +using CoverDataMapIterator = typename CoverDataMap::const_iterator; typedef QList CoverList; /** * This class is used to drag covers in JuK. It adds a special mimetype that * contains the cover ID used for this cover, and also supports an image/png * mimetype for dragging to other applications. * * As of this writing the mimetype is application/x-juk-coverid * * @author Michael Pyne */ class CoverDrag : public QMimeData { Q_OBJECT public: CoverDrag(coverKey id); static const char* mimetype(); static bool isCover(const QMimeData *data); // CoverDrag stores QByteArray data for the cover id, this can convert it // back. static coverKey idFromData(const QMimeData *data); }; /** * This class holds all of the cover art, and manages looking it up by artist * and/or album. This class is similar to a singleton class, but instead all * of the methods are static. This way you can invoke methods like this: * \code * CoverManager::method() * \endcode * instead of using: * \code * CoverManager::instance()->method() * \endcode * * @author Michael Pyne */ class CoverManager { public: /// The set of different sizes you can request a pixmap as. typedef enum { Thumbnail, FullSize } Size; /** * Tries to match @p artist and @p album to a cover in the database. * * @param artist The artist to look for matching covers on. * @param album The album to look for matching covers on. * @return NoMatch if no match could be found, otherwise the id of the * cover art that matches the given metadata. */ static coverKey idFromMetadata(const QString &artist, const QString &album); /** * Returns the cover art for @p id. * * @param id The id of the cover. * @param size The size to return it as. Note that FullSize doesn't * necessarily mean the pixmap is large, so you may need to * scale it up. * @return QPixmap::null if there is no cover art for @p id, otherwise the * cover art. */ static QPixmap coverFromId(coverKey id, Size size = Thumbnail); /** * Returns the cover art for @p ptr. This function is intended for use * by CoverData. * - * @param ptr The CoverData to get the cover of. Note that it is a - * CoverData, not CoverDataPtr. + * @param ptr The CoverData to get the cover of. * @param size The size to return it as. * @see CoverData */ static QPixmap coverFromData(const CoverData &coverData, Size size = Thumbnail); /** * Returns the full suite of information known about the cover given by * @p id. * * @param id the id of the cover to retrieve info on. * @return 0 if there is no info on @p id, otherwise its information. */ - static CoverDataPtr coverInfo(coverKey id); + static CoverData coverInfo(coverKey id); /** * Adds @p large to the cover database, associating with it @p artist and * @p album. * * @param large The full size cover (the thumbnail is automatically * generated). * @param artist The artist of the new cover. * @param album The album of the new cover. */ static coverKey addCover(const QPixmap &large, const QString &artist = "", const QString &album = ""); /** * Adds the file pointed to by the local path @p path to the database, * associating it with @p artist and @p album. * * @param path The absolute path to the fullsize cover art. * @param artist The artist of the new cover. * @param album The album of the new cover. */ static coverKey addCover(const KUrl &path, const QString &artist = "", const QString &album = ""); /** * Function to determine if @p id matches any covers in the database. * * @param id The id of the cover to search for. * @return true if the database has a cover identified by @p id, false * otherwise. */ static bool hasCover(coverKey id); /** * Removes the cover identified by @p id. * * @param id the id of the cover to remove. * @return true if the removal was successful, false if unsuccessful or if * the cover didn't exist. */ static bool removeCover(coverKey id); /** * Replaces the cover art for the cover identified by @p id with @p large. * Any other metadata such as artist and album is unchanged. * * @param id The id of the cover to replace. * @param large The full size cover art for the new cover. */ static bool replaceCover(coverKey id, const QPixmap &large); /** * Saves the current CoverManager information to disk. Changes are not * automatically written to disk due to speed issues, so you can * periodically call this function while running to reduce the chance of * lost data in the event of a crash. */ static void saveCovers(); /** * This is a hack, as we should be shut down automatically by * KStaticDeleter, but JuK is crashing for me on shutdown before * KStaticDeleter gets a chance to run, which is cramping my testing. */ static void shutdown(); /** * @return Iterator pointing to the first element in the cover database. */ static CoverDataMapIterator begin(); /** * @return Iterator pointing after the last element in the cover database. */ static CoverDataMapIterator end(); - /** - * @return A list of all of the id's listed in the database. - */ - static CoverList keys(); - /** * Associates @p path with the cover identified by @id. No comparison of * metadata is performed to enforce this matching. * * @param path The absolute file path to the track. * @param id The identifier of the cover to use with @p path. */ static void setIdForTrack(const QString &path, coverKey id); /** * Returns the identifier of the cover for the track at @p path. * * @param path The absolute file path to the track. * @return NoMatch if @p path doesn't have a cover, otherwise the id of * its cover. */ static coverKey idForTrack(const QString &path); /** * This identifier is used to indicate that no cover was found in the * database. */ static const coverKey NoMatch; private: friend class CoverProxy; // Our QObject-wielding friend. /// Called by CoverProxy to notify of a completed job. static void jobComplete(KJob *job, bool completedSatisfactory); static CoverManagerPrivate *data(); static QPixmap createThumbnail(const QPixmap &base); }; #endif /* JUK_COVERMANAGER_H */ // vim: set et sw=4 tw=0 sta: diff --git a/dbuscollectionproxy.cpp b/dbuscollectionproxy.cpp index 4b172dcc..6e9fb1c6 100644 --- a/dbuscollectionproxy.cpp +++ b/dbuscollectionproxy.cpp @@ -1,158 +1,158 @@ /** * Copyright (C) 2009 Michael Pyne * * This program 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 2 of the License, or (at your option) any later * version. * * This program 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 * this program. If not, see . */ #include "dbuscollectionproxy.h" #include #include #include #include #include #include #include "collectionadaptor.h" #include "playlistcollection.h" #include "covermanager.h" #include "collectionlist.h" #include "coverinfo.h" #include "filehandle.h" DBusCollectionProxy::DBusCollectionProxy (QObject *parent, PlaylistCollection *collection) : QObject(parent), m_collection(collection) { setObjectName( QLatin1String("DBusCollectionProxy" )); new CollectionAdaptor(this); QDBusConnection::sessionBus().registerObject("/Collection",this); } DBusCollectionProxy::~DBusCollectionProxy() { // Clean's the way to be if(!m_lastCover.isEmpty()) QFile::remove(m_lastCover); } void DBusCollectionProxy::openFile(const QString &file) { m_collection->open(QStringList(file)); } void DBusCollectionProxy::openFile(const QStringList &files) { m_collection->open(files); } void DBusCollectionProxy::openFile(const QString &playlist, const QString &file) { m_collection->open(playlist, QStringList(file)); } void DBusCollectionProxy::openFile(const QString &playlist, const QStringList &files) { m_collection->open(playlist, files); } QString DBusCollectionProxy::visiblePlaylist() { return m_collection->playlist(); } QString DBusCollectionProxy::playingPlaylist() { return m_collection->playingPlaylist(); } QStringList DBusCollectionProxy::playlists() { return m_collection->playlists(); } QStringList DBusCollectionProxy::playlistTracks(const QString &playlist) { return m_collection->playlistTracks(playlist); } QString DBusCollectionProxy::trackProperty(const QString &file, const QString &property) { return m_collection->trackProperty(file, property); } void DBusCollectionProxy::createPlaylist(const QString &name) { m_collection->createPlaylist(name); } void DBusCollectionProxy::setPlaylist(const QString &name) { m_collection->setPlaylist(name); } void DBusCollectionProxy::remove() { m_collection->remove(); } void DBusCollectionProxy::removeTrack(const QString &playlist, const QStringList &files) { m_collection->removeTrack(playlist, files); } QString DBusCollectionProxy::trackCover(const QString &track) { coverKey id = CoverManager::idForTrack(track); if(id != CoverManager::NoMatch) { - CoverDataPtr coverData = CoverManager::coverInfo(id); - return coverData->path; + CoverData coverData = CoverManager::coverInfo(id); + return coverData.path; } // No cover, let's see if one is embedded. CollectionListItem *collectionItem = CollectionList::instance()->lookup(track); if(!collectionItem) return QString(); CoverInfo *coverInfo = collectionItem->file().coverInfo(); if(!coverInfo) return QString(); QPixmap cover = coverInfo->pixmap(CoverInfo::FullSize); if(cover.isNull()) return QString(); // We have a cover, extract it and save it to a temporary file. KTemporaryFile tempFile; tempFile.setSuffix(".png"); tempFile.setAutoRemove(false); if(!tempFile.open()) { kError() << "Unable to open temporary file for embedded cover art."; return QString(); } // Save last file name cover, remove it if it's there so that we don't fill // the temp directory with pixmaps. if(!m_lastCover.isEmpty()) QFile::remove(m_lastCover); m_lastCover = tempFile.fileName(); cover.save(&tempFile, "PNG"); return tempFile.fileName(); } // vim: set et sw=4 tw=0 sta: