/*************************************************************************** begin : Sun May 15 2005 copyright : (C) 2005 by Michael Pyne email : michael.pyne@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "covermanager.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 TQDict TrackLookupMap; // This is responsible for making sure that the CoverManagerPrivate class // gets properly destructed on shutdown. static KStaticDeleter sd; const char *CoverDrag::mimetype = "application/x-juk-coverid"; // Caches the TQPixmaps for the covers so that the covers are not all kept in // memory for no reason. typedef TQCache CoverPixmapCache; CoverManagerPrivate *CoverManager::m_data = 0; // Used to save and load CoverData from a TQDataStream TQDataStream &operator<<(TQDataStream &out, const CoverData &data); TQDataStream &operator>>(TQDataStream &in, CoverData &data); // // Implementation of CoverData struct // TQPixmap CoverData::pixmap() const { return CoverManager::coverFromData(*this, CoverManager::FullSize); } TQPixmap 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 CoverDataMap covers; /// Maps file names to coverKey id's. TrackLookupMap tracks; /// A cache of the cover representations. The key format is: /// 'f' followed by the pathname for FullSize covers, and /// 't' followed by the pathname for Thumbnail covers. CoverPixmapCache pixmapCache; CoverManagerPrivate() : tracks(1301), pixmapCache(2 * 1024 * 768) { loadCovers(); pixmapCache.setAutoDelete(true); } ~CoverManagerPrivate() { 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; private: void loadCovers(); /** * @return the full path and filename of the file storing the cover * lookup map and the translations between pathnames and ids. */ TQString coverLocation() const; }; // // Implementation of CoverManagerPrivate methods. // void CoverManagerPrivate::createDataDir() const { TQDir dir; TQString dirPath(TQDir::cleanDirPath(coverLocation() + "/..")); if(!dir.exists(dirPath)) TDEStandardDirs::makeDir(dirPath); } void CoverManagerPrivate::saveCovers() const { kdDebug() << k_funcinfo << endl; // Make sure the directory exists first. createDataDir(); TQFile file(coverLocation()); kdDebug() << "Opening covers db: " << coverLocation() << endl; if(!file.open(IO_WriteOnly)) { kdError() << "Unable to save covers to disk!\n"; return; } TQDataStream out(&file); // Write out the version and count out << TQ_UINT32(0) << TQ_UINT32(covers.count()); // Write out the data for(CoverDataMap::ConstIterator it = covers.begin(); it != covers.end(); ++it) { out << TQ_UINT32(it.key()); out << *it.data(); } // Now write out the track mapping. out << TQ_UINT32(tracks.count()); TQDictIterator trackMapIt(tracks); while(trackMapIt.current()) { out << trackMapIt.currentKey() << TQ_UINT32(*trackMapIt.current()); ++trackMapIt; } } void CoverManagerPrivate::loadCovers() { kdDebug() << k_funcinfo << endl; TQFile file(coverLocation()); if(!file.open(IO_ReadOnly)) { // Guess we don't have any covers yet. return; } TQDataStream in(&file); TQ_UINT32 count, version; // First thing we'll read in will be the version. // Only version 0 is defined for now. in >> version; if(version > 0) { kdError() << "Cover database was created by a higher version of JuK,\n"; kdError() << "I don't know what to do with it.\n"; return; } // Read in the count next, then the data. in >> count; for(TQ_UINT32 i = 0; i < count; ++i) { // Read the id, and 3 TQStrings for every 1 of the count. TQ_UINT32 id; CoverDataPtr data(new CoverData); in >> id; in >> *data; data->refCount = 0; covers[(coverKey) id] = data; } in >> count; for(TQ_UINT32 i = 0; i < count; ++i) { TQString path; TQ_UINT32 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(!tracks.find(path)) { ++covers[(coverKey) id]->refCount; // Another track using this. tracks.insert(path, new coverKey(id)); } } } TQString CoverManagerPrivate::coverLocation() const { return TDEGlobal::dirs()->saveLocation("appdata") + "coverdb/covers"; } // XXX: This could probably use some improvement, I don't like the linear // search for ID idea. coverKey CoverManagerPrivate::nextId() const { // Start from 1... coverKey key = 1; while(covers.contains(key)) ++key; return key; } // // Implementation of CoverDrag // CoverDrag::CoverDrag(coverKey id, TQWidget *src) : TQDragObject(src, "coverDrag"), m_id(id) { TQPixmap cover = CoverManager::coverFromId(id); if(!cover.isNull()) setPixmap(cover); } const char *CoverDrag::format(int i) const { if(i == 0) return mimetype; if(i == 1) return "image/png"; return 0; } TQByteArray CoverDrag::encodedData(const char *mimetype) const { if(qstrcmp(CoverDrag::mimetype, mimetype) == 0) { TQByteArray data; TQDataStream ds(data, IO_WriteOnly); ds << TQ_UINT32(m_id); return data; } else if(qstrcmp(mimetype, "image/png") == 0) { TQPixmap large = CoverManager::coverFromId(m_id, CoverManager::FullSize); TQImage img = large.convertToImage(); TQByteArray data; TQBuffer buffer(data); buffer.open(IO_WriteOnly); img.save(&buffer, "PNG"); // Write in PNG format. return data; } return TQByteArray(); } bool CoverDrag::canDecode(const TQMimeSource *e) { return e->provides(mimetype); } bool CoverDrag::decode(const TQMimeSource *e, coverKey &id) { if(!canDecode(e)) return false; TQByteArray data = e->encodedData(mimetype); TQDataStream ds(data, IO_ReadOnly); TQ_UINT32 i; ds >> i; id = (coverKey) i; return true; } // // Implementation of CoverManager methods. // coverKey CoverManager::idFromMetadata(const TQString &artist, const TQString &album) { // Search for the string, yay! It might make sense to use a cache here, // if so it's not hard to add a TQCache. CoverDataMap::ConstIterator it = begin(); CoverDataMap::ConstIterator endIt = end(); for(; it != endIt; ++it) { if(it.data()->album == album.lower() && it.data()->artist == artist.lower()) return it.key(); } return NoMatch; } TQPixmap CoverManager::coverFromId(coverKey id, Size size) { CoverDataPtr info = coverInfo(id); if(!info) return TQPixmap(); if(size == Thumbnail) return info->thumbnail(); return info->pixmap(); } TQPixmap CoverManager::coverFromData(const CoverData &coverData, Size size) { TQString 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. TQPixmap *pix = data()->pixmapCache[path]; if(pix) { kdDebug(65432) << "Found pixmap in cover cache.\n"; return *pix; } // Not in cache, load it and add it. pix = new TQPixmap(coverData.path); if(pix->isNull()) return TQPixmap(); if(size == Thumbnail) { // Convert to image for smoothScale() TQImage image = pix->convertToImage(); pix->convertFromImage(image.smoothScale(80, 80, TQ_ScaleMin)); } TQPixmap returnValue = *pix; // Save it early. if(!data()->pixmapCache.insert(path, pix, pix->height() * pix->width())) delete pix; return returnValue; } coverKey CoverManager::addCover(const TQPixmap &large, const TQString &artist, const TQString &album) { kdDebug() << k_funcinfo << endl; coverKey id = data()->nextId(); CoverDataPtr coverData(new CoverData); if(large.isNull()) { kdDebug() << "The pixmap you're trying to add is NULL!\n"; return NoMatch; } // Save it to file first! TQString ext = TQString("/coverdb/coverID-%1.png").arg(id); coverData->path = TDEGlobal::dirs()->saveLocation("appdata") + ext; kdDebug() << "Saving pixmap to " << coverData->path << endl; data()->createDataDir(); if(!large.save(coverData->path, "PNG")) { kdError() << "Unable to save pixmap to " << coverData->path << endl; return NoMatch; } coverData->artist = artist.lower(); coverData->album = album.lower(); coverData->refCount = 0; data()->covers[id] = coverData; // Make sure the new cover isn't inadvertently cached. data()->pixmapCache.remove(TQString("f%1").arg(coverData->path)); data()->pixmapCache.remove(TQString("t%1").arg(coverData->path)); return id; } coverKey CoverManager::addCover(const TQString &path, const TQString &artist, const TQString &album) { return addCover(TQPixmap(path), artist, album); } bool CoverManager::hasCover(coverKey id) { return data()->covers.contains(id); } bool CoverManager::removeCover(coverKey id) { if(!hasCover(id)) return false; // Remove cover from cache. CoverDataPtr coverData = coverInfo(id); data()->pixmapCache.remove(TQString("f%1").arg(coverData->path)); data()->pixmapCache.remove(TQString("t%1").arg(coverData->path)); // Remove references to files that had that track ID. TQDictIterator it(data()->tracks); for(; it.current(); ++it) if(*it.current() == id) data()->tracks.remove(it.currentKey()); // Remove covers from disk. TQFile::remove(coverData->path); // Finally, forget that we ever knew about this cover. data()->covers.remove(id); return true; } bool CoverManager::replaceCover(coverKey id, const TQPixmap &large) { if(!hasCover(id)) return false; CoverDataPtr coverData = coverInfo(id); // Empty old pixmaps from cache. data()->pixmapCache.remove(TQString("%1%2").arg("t", coverData->path)); data()->pixmapCache.remove(TQString("%1%2").arg("f", coverData->path)); large.save(coverData->path, "PNG"); return true; } CoverManagerPrivate *CoverManager::data() { if(!m_data) sd.setObject(m_data, new CoverManagerPrivate); return m_data; } void CoverManager::saveCovers() { data()->saveCovers(); } void CoverManager::shutdown() { sd.destructObject(); } CoverDataMap::ConstIterator CoverManager::begin() { return data()->covers.constBegin(); } CoverDataMap::ConstIterator CoverManager::end() { return data()->covers.constEnd(); } TQValueList CoverManager::keys() { return data()->covers.keys(); } void CoverManager::setIdForTrack(const TQString &path, coverKey id) { coverKey *oldId = data()->tracks.find(path); if(oldId && (id == *oldId)) return; // We're already done. if(oldId && *oldId != NoMatch) { data()->covers[*oldId]->refCount--; data()->tracks.remove(path); if(data()->covers[*oldId]->refCount == 0) { kdDebug(65432) << "Cover " << *oldId << " is unused, removing.\n"; removeCover(*oldId); } } if(id != NoMatch) { data()->covers[id]->refCount++; data()->tracks.insert(path, new coverKey(id)); } } coverKey CoverManager::idForTrack(const TQString &path) { coverKey *coverPtr = data()->tracks.find(path); if(!coverPtr) return NoMatch; return *coverPtr; } CoverDataPtr CoverManager::coverInfo(coverKey id) { if(data()->covers.contains(id)) return data()->covers[id]; return CoverDataPtr(0); } /** * 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. */ TQDataStream &operator<<(TQDataStream &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. */ TQDataStream &operator>>(TQDataStream &in, CoverData &data) { in >> data.artist; in >> data.album; in >> data.path; return in; } // vim: set et sw=4 ts=4: