/* * Cantata * * Copyright (c) 2011-2012 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC 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 QtMPC. If not, see . */ #include "musiclibraryitemalbum.h" #include "musiclibraryitemartist.h" #include "musiclibraryitemsong.h" #include "musiclibraryitemroot.h" #include "musiclibrarymodel.h" #include "albumsmodel.h" #include "playqueuemodel.h" #include "settings.h" #include "config.h" #include "covers.h" #include "itemview.h" #include "mpdparseutils.h" #include "mpdconnection.h" #include "localize.h" #include "utils.h" #include "icons.h" #include #include #include #include #include #include #include #include #ifdef ENABLE_KDE_SUPPORT #include #endif #ifdef ENABLE_KDE_SUPPORT K_GLOBAL_STATIC(MusicLibraryModel, instance) #endif MusicLibraryModel * MusicLibraryModel::self() { #ifdef ENABLE_KDE_SUPPORT return instance; #else static MusicLibraryModel *instance=0; if(!instance) { instance=new MusicLibraryModel; } return instance; #endif } static const QLatin1String constLibraryCache("library/"); static const QLatin1String constLibraryExt(".xml"); static const QString cacheFileName() { QString fileName=MPDConnection::self()->getDetails().hostname+constLibraryExt; fileName.replace('/', '_'); return Utils::cacheDir(constLibraryCache)+fileName; } MusicLibraryModel::MusicLibraryModel(QObject *parent) : QAbstractItemModel(parent) , artistImages(false) , rootItem(new MusicLibraryItemRoot) { connect(Covers::self(), SIGNAL(artistImage(const QString &, const QImage &)), this, SLOT(setArtistImage(const QString &, const QImage &))); connect(Covers::self(), SIGNAL(cover(const Song &, const QImage &, const QString &)), this, SLOT(setCover(const Song &, const QImage &, const QString &))); } MusicLibraryModel::~MusicLibraryModel() { delete rootItem; } QModelIndex MusicLibraryModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } const MusicLibraryItem * parentItem; if (!parent.isValid()) { parentItem = rootItem; } else { parentItem = static_cast(parent.internalPointer()); } MusicLibraryItem * const childItem = parentItem->childItem(row); if (childItem) { return createIndex(row, column, childItem); } return QModelIndex(); } QModelIndex MusicLibraryModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } const MusicLibraryItem * const childItem = static_cast(index.internalPointer()); MusicLibraryItem * const parentItem = childItem->parentItem(); if (parentItem == rootItem) { return QModelIndex(); } return createIndex(parentItem->row(), 0, parentItem); } QVariant MusicLibraryModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section) return Qt::Horizontal==orientation && Qt::DisplayRole==role ? rootItem->data() : QVariant(); } int MusicLibraryModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) { return 0; } const MusicLibraryItem *parentItem; if (!parent.isValid()) { parentItem = rootItem; } else { parentItem = static_cast(parent.internalPointer()); } return parentItem->childCount(); } int MusicLibraryModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) { return static_cast(parent.internalPointer())->columnCount(); } else { return rootItem->columnCount(); } } QVariant MusicLibraryModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } MusicLibraryItem *item = static_cast(index.internalPointer()); switch (role) { case Qt::DecorationRole: switch (item->itemType()) { case MusicLibraryItem::Type_Artist: { MusicLibraryItemArtist *artist = static_cast(item); if (artistImages) { return artist->cover(); } else { return artist->isVarious() ? Icons::variousArtistsIcon : Icons::artistIcon; } } case MusicLibraryItem::Type_Album: if (MusicLibraryItemAlbum::CoverNone==MusicLibraryItemAlbum::currentCoverSize()) { return Icons::albumIcon; } else { return static_cast(item)->cover(); } case MusicLibraryItem::Type_Song: return Icon(Song::Playlist==static_cast(item)->song().type ? "view-media-playlist" : "audio-x-generic"); default: return QVariant(); } case Qt::DisplayRole: if (MusicLibraryItem::Type_Song==item->itemType()) { MusicLibraryItemSong *song = static_cast(item); if (Song::Playlist==song->song().type) { return song->song().title; } if (static_cast(song->parentItem())->isSingleTracks()) { return song->song().artistSong(); } else { return song->song().trackAndTitleStr(static_cast(song->parentItem()->parentItem())->isVarious() && !Song::isVariousArtists(song->song().artist)); } } else if(MusicLibraryItem::Type_Album==item->itemType() && MusicLibraryItemAlbum::showDate() && static_cast(item)->year()>0) { return QString::number(static_cast(item)->year())+QLatin1String(" - ")+item->data(); } return item->data(); case Qt::ToolTipRole: switch (item->itemType()) { case MusicLibraryItem::Type_Artist: return 0==item->childCount() ? item->data() : item->data()+"
"+ #ifdef ENABLE_KDE_SUPPORT i18np("1 Album", "%1 Albums", item->childCount()); #else QTP_ALBUMS_STR(item->childCount()); #endif case MusicLibraryItem::Type_Album: return item->parentItem()->data()+QLatin1String("
")+(0==item->childCount() ? item->data() : item->data()+"
"+ #ifdef ENABLE_KDE_SUPPORT i18np("1 Track (%2)", "%1 Tracks (%2)", item->childCount(), Song::formattedTime(static_cast(item)->totalTime())) #else QTP_TRACKS_DURATION_STR(item->childCount(), Song::formattedTime(static_cast(item)->totalTime())) #endif ); case MusicLibraryItem::Type_Song: { return item->parentItem()->parentItem()->data()+QLatin1String("
")+item->parentItem()->data()+QLatin1String("
")+ data(index, Qt::DisplayRole).toString()+QLatin1String("
")+ (Song::Playlist==static_cast(item)->song().type ? QString() : Song::formattedTime(static_cast(item)->time())+QLatin1String("
"))+ QLatin1String("")+static_cast(item)->song().file+QLatin1String(""); } default: return QVariant(); } case ItemView::Role_ImageSize: if (MusicLibraryItem::Type_Song!=item->itemType() && !MusicLibraryItemAlbum::itemSize().isNull()) { // icon/list style view... return MusicLibraryItemAlbum::iconSize(rootItem->useLargeImages()); } else if (MusicLibraryItem::Type_Album==item->itemType() || (artistImages && MusicLibraryItem::Type_Artist==item->itemType())) { return MusicLibraryItemAlbum::iconSize(); } break; case ItemView::Role_SubText: switch (item->itemType()) { case MusicLibraryItem::Type_Artist: #ifdef ENABLE_KDE_SUPPORT return i18np("1 Album", "%1 Albums", item->childCount()); #else return QTP_ALBUMS_STR(item->childCount()); #endif break; case MusicLibraryItem::Type_Song: return Song::formattedTime(static_cast(item)->time()); case MusicLibraryItem::Type_Album: #ifdef ENABLE_KDE_SUPPORT return i18np("1 Track (%2)", "%1 Tracks (%2)", item->childCount(), Song::formattedTime(static_cast(item)->totalTime())); #else return QTP_TRACKS_DURATION_STR(item->childCount(), Song::formattedTime(static_cast(item)->totalTime())); #endif default: return QVariant(); } case ItemView::Role_Image: if (MusicLibraryItem::Type_Album==item->itemType()) { QVariant v; v.setValue(static_cast(item)->cover()); return v; } else if (MusicLibraryItem::Type_Artist==item->itemType() && artistImages) { QVariant v; v.setValue(static_cast(item)->cover()); return v; } case Qt::SizeHintRole: if (!artistImages && MusicLibraryItem::Type_Artist==item->itemType()) { return QVariant(); } if (rootItem->useLargeImages() && MusicLibraryItem::Type_Song!=item->itemType() && !MusicLibraryItemAlbum::itemSize().isNull()) { return MusicLibraryItemAlbum::itemSize(); } default: return QVariant(); } return QVariant(); } void MusicLibraryModel::clear() { const MusicLibraryItemRoot *oldRoot = rootItem; beginResetModel(); databaseTime = QDateTime(); rootItem = new MusicLibraryItemRoot; rootItem->setLargeImages(oldRoot->useLargeImages()); delete oldRoot; endResetModel(); // emit updated(rootItem); AlbumsModel::self()->update(rootItem); //emit updateGenres(QSet()); } QModelIndex MusicLibraryModel::findSongIndex(const Song &s) const { MusicLibraryItemArtist *artistItem = rootItem->artist(s, false); if (artistItem) { MusicLibraryItemAlbum *albumItem = artistItem->album(s, false); if (albumItem) { foreach (MusicLibraryItem *songItem, albumItem->childItems()) { if (songItem->data()==s.title) { return createIndex(albumItem->childItems().indexOf(songItem), 0, songItem); } } } } return QModelIndex(); } const MusicLibraryItem * MusicLibraryModel::findSong(const Song &s) const { MusicLibraryItemArtist *artistItem = rootItem->artist(s, false); if (artistItem) { MusicLibraryItemAlbum *albumItem = artistItem->album(s, false); if (albumItem) { foreach (const MusicLibraryItem *songItem, albumItem->childItems()) { if (songItem->data()==s.title) { return songItem; } } } } return 0; } bool MusicLibraryModel::songExists(const Song &s) const { const MusicLibraryItem *song=findSong(s); if (song) { return true; } if (!s.isVariousArtists()) { Song mod(s); mod.albumartist=i18n("Various Artists"); if (MPDParseUtils::groupMultiple()) { song=findSong(mod); if (song) { Song sng=static_cast(song)->song(); if (sng.albumArtist()==s.albumArtist()) { return true; } } } if (MPDParseUtils::groupSingle()) { mod.album=i18n("Single Tracks"); song=findSong(mod); if (song) { Song sng=static_cast(song)->song(); if (sng.albumArtist()==s.albumArtist() && sng.album==s.album) { return true; } } } } return false; } bool MusicLibraryModel::updateSong(const Song &orig, const Song &edit) { if (orig.albumArtist()==edit.albumArtist() && orig.album==edit.album) { MusicLibraryItemArtist *artistItem = rootItem->artist(orig, false); if (!artistItem) { return false; } MusicLibraryItemAlbum *albumItem = artistItem->album(orig, false); if (!albumItem) { return false; } int songRow=0; foreach (MusicLibraryItem *song, albumItem->childItems()) { if (static_cast(song)->song()==orig) { static_cast(song)->setSong(edit); QModelIndex idx=index(songRow, 0, index(artistItem->childItems().indexOf(albumItem), 0, index(rootItem->childItems().indexOf(artistItem), 0, QModelIndex()))); emit dataChanged(idx, idx); return true; } songRow++; } } return false; } void MusicLibraryModel::addSongToList(const Song &s) { // databaseTime=QDateTime(); MusicLibraryItemArtist *artistItem = rootItem->artist(s, false); if (!artistItem) { beginInsertRows(QModelIndex(), rootItem->childCount(), rootItem->childCount()); artistItem = rootItem->createArtist(s); endInsertRows(); } MusicLibraryItemAlbum *albumItem = artistItem->album(s, false); if (!albumItem) { beginInsertRows(createIndex(rootItem->childItems().indexOf(artistItem), 0, artistItem), artistItem->childCount(), artistItem->childCount()); albumItem = artistItem->createAlbum(s); endInsertRows(); } quint32 year=albumItem->year(); foreach (const MusicLibraryItem *songItem, albumItem->childItems()) { const MusicLibraryItemSong *song=static_cast(songItem); if (song->track()==s.track && song->disc()==s.disc && song->data()==s.title) { return; } } beginInsertRows(createIndex(artistItem->childItems().indexOf(albumItem), 0, albumItem), albumItem->childCount(), albumItem->childCount()); MusicLibraryItemSong *songItem = new MusicLibraryItemSong(s, albumItem); albumItem->append(songItem); rootItem->addGenre(s.genre); endInsertRows(); if (year!=albumItem->year()) { QModelIndex idx=index(artistItem->childItems().indexOf(albumItem), 0, index(rootItem->childItems().indexOf(artistItem), 0, QModelIndex())); emit dataChanged(idx, idx); } } void MusicLibraryModel::removeSongFromList(const Song &s) { MusicLibraryItemArtist *artistItem = rootItem->artist(s, false); if (!artistItem) { return; } MusicLibraryItemAlbum *albumItem = artistItem->album(s, false); if (!albumItem) { return; } MusicLibraryItem *songItem=0; int songRow=0; foreach (MusicLibraryItem *song, albumItem->childItems()) { if (static_cast(song)->song().title==s.title) { songItem=song; break; } songRow++; } if (!songItem) { return; } // databaseTime=QDateTime(); if (1==artistItem->childCount() && 1==albumItem->childCount()) { // 1 album with 1 song - so remove whole artist int row=rootItem->childItems().indexOf(artistItem); beginRemoveRows(QModelIndex(), row, row); rootItem->remove(artistItem); endRemoveRows(); return; } if (1==albumItem->childCount()) { // multiple albums, but this album only has 1 song - remove album int row=artistItem->childItems().indexOf(albumItem); beginRemoveRows(createIndex(rootItem->childItems().indexOf(artistItem), 0, artistItem), row, row); artistItem->remove(albumItem); endRemoveRows(); return; } // Just remove particular song beginRemoveRows(createIndex(artistItem->childItems().indexOf(albumItem), 0, albumItem), songRow, songRow); quint32 year=albumItem->year(); albumItem->remove(songRow); endRemoveRows(); if (year!=albumItem->year()) { QModelIndex idx=index(artistItem->childItems().indexOf(albumItem), 0, index(rootItem->childItems().indexOf(artistItem), 0, QModelIndex())); emit dataChanged(idx, idx); } } void MusicLibraryModel::updateSongFile(const Song &from, const Song &to) { rootItem->updateSongFile(from, to); } void MusicLibraryModel::removeCache() { //Check if dir exists QString cacheFile(cacheFileName()); if (QFile::exists(cacheFile)) { QFile::remove(cacheFile); } databaseTime = QDateTime(); } void MusicLibraryModel::getDetails(QSet &artists, QSet &albumArtists, QSet &albums, QSet &genres) { rootItem->getDetails(artists, albumArtists, albums, genres); } void MusicLibraryModel::updateMusicLibrary(MusicLibraryItemRoot *newroot, QDateTime dbUpdate, bool fromFile) { if (databaseTime >= dbUpdate) { delete newroot; return; } bool updatedSongs=false; bool needToSave=dbUpdate>databaseTime; bool incremental=rootItem->childCount() && newroot->childCount(); bool needToUpdate=databaseTime.isNull(); if (incremental && !QFile::exists(cacheFileName())) { incremental=false; } if (incremental) { updatedSongs=update(newroot->allSongs()); delete newroot; databaseTime = dbUpdate; } else { const MusicLibraryItemRoot *oldRoot = rootItem; beginResetModel(); databaseTime = dbUpdate; rootItem = newroot; rootItem->setLargeImages(oldRoot->useLargeImages()); delete oldRoot; endResetModel(); updatedSongs=true; } if (updatedSongs || needToUpdate) { if (!fromFile && (needToSave || needToUpdate)) { toXML(rootItem, dbUpdate); } AlbumsModel::self()->update(rootItem); // emit updated(rootItem); emit updateGenres(rootItem->genres()); } } bool MusicLibraryModel::update(const QSet &songs) { QSet currentSongs=rootItem->allSongs(); QSet updateSongs=songs; QSet removed=currentSongs-updateSongs; QSet added=updateSongs-currentSongs; bool updatedSongs=added.count()||removed.count(); foreach (const Song &s, removed) { removeSongFromList(s); } foreach (const Song &s, added) { addSongToList(s); } return updatedSongs; } void MusicLibraryModel::setArtistImage(const QString &artist, const QImage &img) { if (img.isNull() || MusicLibraryItemAlbum::CoverNone==MusicLibraryItemAlbum::currentCoverSize()) { return; } Song song; song.artist=song.albumartist=artist; MusicLibraryItemArtist *artistItem = rootItem->artist(song, false); if (artistItem && static_cast(artistItem)->setCover(img)) { QModelIndex idx=index(rootItem->childItems().indexOf(artistItem), 0, QModelIndex()); emit dataChanged(idx, idx); } } void MusicLibraryModel::setCover(const Song &song, const QImage &img, const QString &file) { Q_UNUSED(file) if (img.isNull() || MusicLibraryItemAlbum::CoverNone==MusicLibraryItemAlbum::currentCoverSize()) { return; } MusicLibraryItemArtist *artistItem = rootItem->artist(song, false); if (artistItem) { MusicLibraryItemAlbum *albumItem = artistItem->album(song, false); if (albumItem && static_cast(albumItem)->setCover(img)) { QModelIndex idx=index(artistItem->childItems().indexOf(albumItem), 0, index(rootItem->childItems().indexOf(artistItem), 0, QModelIndex())); emit dataChanged(idx, idx); } } // int i=0; // foreach (const MusicLibraryItem *artistItem, rootItem->childItems()) { // if (artistItem->data()==artist) { // int j=0; // foreach (const MusicLibraryItem *albumItem, artistItem->childItems()) { // if (albumItem->data()==album) { // if (static_cast(albumItem)->setCover(img)) { // QModelIndex idx=index(j, 0, index(i, 0, QModelIndex())); // emit dataChanged(idx, idx); // } // return; // } // j++; // } // } // i++; // } } /** * Writes the musiclibrarymodel to and xml file so we can store it on * disk for faster startup the next time * * @param filename The name of the file to write the xml to */ void MusicLibraryModel::toXML(const MusicLibraryItemRoot *root, const QDateTime &date) { root->toXML(cacheFileName(), date); } /** * Read an xml file from disk. * * @param filename The name of the xmlfile to read the db from * * @return true on succesfull parsing, false otherwise * TODO: check for hostname * TODO: check for database version */ bool MusicLibraryModel::fromXML() { MusicLibraryItemRoot *root=new MusicLibraryItemRoot; quint32 date=root->fromXML(cacheFileName(), MPDStats::self()->dbUpdate()); if (!date) { delete root; return false; } QDateTime dt; dt.setTime_t(date); updateMusicLibrary(root, dt, true); return true; } Qt::ItemFlags MusicLibraryModel::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled; } else { return Qt::ItemIsDropEnabled; } } QStringList MusicLibraryModel::filenames(const QModelIndexList &indexes, bool allowPlaylists) const { QList songList=songs(indexes, allowPlaylists); QStringList fnames; foreach (const Song &s, songList) { fnames.append(s.file); } return fnames; } static inline void addSong(const MusicLibraryItem *song, QList &insertInto, QList &checkAgainst, bool allowPlaylists) { if (MusicLibraryItem::Type_Song==song->itemType() && (allowPlaylists || Song::Playlist!=static_cast(song)->song().type) && !checkAgainst.contains(static_cast(song)->song())) { static_cast(song)->song().updateSize(MPDConnection::self()->getDetails().dir); insertInto << static_cast(song)->song(); } } QList MusicLibraryModel::songs(const QModelIndexList &indexes, bool allowPlaylists) const { QList songs; foreach(QModelIndex index, indexes) { const MusicLibraryItem *item = static_cast(index.internalPointer()); switch (item->itemType()) { case MusicLibraryItem::Type_Artist: { // First, sort all albums as they would appear in UI... QList artistAlbums=static_cast(item)->childItems(); qSort(artistAlbums.begin(), artistAlbums.end(), MusicLibraryItemAlbum::lessThan); foreach (MusicLibraryItem *i, artistAlbums) { QList albumSongs; const MusicLibraryItemSong *cue=allowPlaylists ? static_cast(i)->getCueFile() : 0; if (cue) { addSong(cue, albumSongs, songs, true); } else { foreach (const MusicLibraryItem *song, static_cast(i)->childItems()) { addSong(song, albumSongs, songs, false); } } qSort(albumSongs); songs << albumSongs; } break; } case MusicLibraryItem::Type_Album: { QList albumSongs; const MusicLibraryItemSong *cue=allowPlaylists ? static_cast(item)->getCueFile() : 0; if (cue) { addSong(cue, albumSongs, songs, true); } else { foreach (const MusicLibraryItem *song, static_cast(item)->childItems()) { addSong(song, albumSongs, songs, false); } } qSort(albumSongs); songs << albumSongs; break; } case MusicLibraryItem::Type_Song: addSong(item, songs, songs, allowPlaylists); break; default: break; } } return songs; } QList MusicLibraryModel::songs(const QStringList &filenames) const { QList songs; if (filenames.isEmpty()) { return songs; } QSet files=filenames.toSet(); foreach (const MusicLibraryItem *artist, static_cast(rootItem)->childItems()) { foreach (const MusicLibraryItem *album, static_cast(artist)->childItems()) { foreach (const MusicLibraryItem *song, static_cast(album)->childItems()) { QSet::Iterator it=files.find(static_cast(song)->file()); if (it!=files.end()) { files.erase(it); songs.append(static_cast(song)->song()); } } } } return songs; } /** * Convert the data at indexes into mimedata ready for transport * * @param indexes The indexes to pack into mimedata * @return The mimedata */ QMimeData *MusicLibraryModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QStringList files=filenames(indexes, true); PlayQueueModel::encode(*mimeData, PlayQueueModel::constFileNameMimeType, files); if (!MPDConnection::self()->getDetails().dir.isEmpty()) { QStringList paths; foreach (const QString &f, files) { paths << MPDConnection::self()->getDetails().dir+f; } PlayQueueModel::encode(*mimeData, PlayQueueModel::constUriMimeType, paths); } return mimeData; }