/* * Cantata * * Copyright (c) 2011-2014 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "musiclibraryitemalbum.h" #include "musiclibraryitemartist.h" #include "musiclibraryitemsong.h" #include "musiclibraryitemroot.h" #include "musiclibraryitempodcast.h" #include "musiclibrarymodel.h" #include "dirviewmodel.h" #include "onlineservicesmodel.h" #include "online/onlineservice.h" #ifdef ENABLE_DEVICES_SUPPORT #include "online/onlinedevice.h" #endif #include "online/jamendoservice.h" #include "online/magnatuneservice.h" #include "online/soundcloudservice.h" #include "online/podcastservice.h" #include "playqueuemodel.h" #include "roles.h" #include "mpd-interface/mpdparseutils.h" #include "support/localize.h" #include "gui/plurals.h" #include "widgets/icons.h" #include "devices/filejob.h" #include "support/utils.h" #include "gui/stdactions.h" #include "support/actioncollection.h" #include "network/networkaccessmanager.h" #include "gui/settings.h" #include "support/globalstatic.h" #ifdef ENABLE_STREAMS #include "streamsmodel.h" #endif #include #include #include #include #if defined ENABLE_MODEL_TEST #include "modeltest.h" #endif QString OnlineServicesModel::constUdiPrefix("online-service://"); GLOBAL_STATIC(OnlineServicesModel, instance) OnlineServicesModel::OnlineServicesModel(QObject *parent) : MultiMusicModel(parent) , enabled(false) , dev(0) , podcast(0) { configureAction = new Action(Icons::self()->configureIcon, i18n("Configure Service"), this); refreshAction = new Action(Icon("view-refresh"), i18n("Refresh Service"), this); subscribeAction = new Action(Icon("list-add"), i18n("Add Subscription"), this); unSubscribeAction = new Action(Icon("list-remove"), i18n("Remove Subscription"), this); refreshSubscriptionAction = new Action(Icon("view-refresh"), i18n("Refresh Subscription"), this); #if defined ENABLE_STREAMS || !defined UNITY_MENU_HACK searchAction = StreamsModel::self()->searchAct(); #else // For Unity we try to hide icons from menubar menus. However, search is used in the menubar AND in the streams view. We // need the icon on the streams view. Therefore, if the StdAction has no icon - we create a new one and forward all signals... if (StdActions::self()->searchAction->icon().isNull()) { searchAction = new Action(Icon("edit-find"), StdActions::self()->searchAction->text(), this); searchAction->setToolTip(StdActions::self()->searchAction->toolTip()); connect(searchAction, SIGNAL(triggered()), StdActions::self()->searchAction, SIGNAL(triggered())); connect(ActionCollection::get(), SIGNAL(tooltipUpdated(QAction *)), SLOT(tooltipUpdated(QAction *))); } else { searchAction = StdActions::self()->searchAction; } #endif load(); #if defined ENABLE_MODEL_TEST new ModelTest(this, this); #endif } OnlineServicesModel::~OnlineServicesModel() { } QModelIndex OnlineServicesModel::serviceIndex(const OnlineService *srv) const { int r=row(const_cast(srv)); return -1==r ? QModelIndex() : createIndex(r, 0, (void *)srv); } bool OnlineServicesModel::hasChildren(const QModelIndex &index) const { return !index.isValid() || MusicLibraryItem::Type_Song!=static_cast(index.internalPointer())->itemType(); } bool OnlineServicesModel::canFetchMore(const QModelIndex &index) const { return index.isValid() && MusicLibraryItem::Type_Root==static_cast(index.internalPointer())->itemType() && !static_cast(index.internalPointer())->isLoaded() && !static_cast(index.internalPointer())->isSearchBased() && !static_cast(index.internalPointer())->isPodcasts(); } void OnlineServicesModel::fetchMore(const QModelIndex &index) { if (!index.isValid()) { return; } if (canFetchMore(index)) { static_cast(index.internalPointer())->reload(); } } QVariant OnlineServicesModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } MusicLibraryItem *item = static_cast(index.internalPointer()); switch (role) { case Qt::ToolTipRole: if (MusicLibraryItem::Type_Root==item->itemType() && 0!=item->childCount() && static_cast(item)->isSearchBased() && !static_cast(item)->currentSearchString().isEmpty()) { return MusicModel::data(index, role).toString()+"
"+i18n("Last Search:%1", static_cast(item)->currentSearchString()); } break; case Cantata::Role_SubText: switch(item->itemType()) { case MusicLibraryItem::Type_Root: { OnlineService *srv=static_cast(item); if (srv->isLoading()) { return static_cast(item)->statusMessage(); } if (srv->isSearching()) { return i18n("Searching..."); } if (!srv->isLoaded() && !srv->isSearchBased() && !srv->isPodcasts()) { return i18n("Not Loaded"); } if (0==item->childCount() && srv->isSearchBased()) { return i18n("Use search to locate tracks"); } if (srv->isSearchBased()) { return Plurals::tracks(item->childCount()); } if (srv->isPodcasts()) { return Plurals::podcasts(item->childCount()); } break; } case MusicLibraryItem::Type_Song: { if (MusicLibraryItem::Type_Podcast==item->parentItem()->itemType()) { if (static_cast(item)->downloadProgress()>=0) { return MultiMusicModel::data(index, role).toString()+QLatin1Char(' ')+ i18n("(Downloading: %1%)", static_cast(item)->downloadProgress()); } else if (MusicLibraryItemPodcastEpisode::QueuedForDownload==static_cast(item)->downloadProgress()) { return MultiMusicModel::data(index, role).toString()+QLatin1Char(' ')+i18n("(Download Queued)"); } } break; } default: break; } break; case Cantata::Role_Actions: { QList actions; if (MusicLibraryItem::Type_Root==item->itemType()) { OnlineService *srv=static_cast(item); if (srv->canConfigure()) { actions << configureAction; } if (srv->canLoad()) { actions << refreshAction; } else if (srv->canSubscribe() && !srv->childItems().isEmpty()) { actions << refreshSubscriptionAction; } if (srv->isSearchBased() || srv->isLoaded()) { actions << searchAction; } if (srv->canSubscribe()) { actions << subscribeAction; } } else if (MusicLibraryItem::Type_Podcast==item->itemType()) { actions << refreshSubscriptionAction; } else { actions << StdActions::self()->replacePlayQueueAction << StdActions::self()->addToPlayQueueAction; } if (!actions.isEmpty()) { QVariant v; v.setValue >(actions); return v; } break; } case Cantata::Role_ListImage: return MusicLibraryItem::Type_Album==item->itemType() || MusicLibraryItem::Type_Podcast==item->itemType(); case Qt::DecorationRole: if (MusicLibraryItem::Type_Song==item->itemType() && item->parentItem() && MusicLibraryItem::Type_Podcast==item->parentItem()->itemType()) { if (!static_cast(item)->localPath().isEmpty()) { return Icons::self()->downloadedPodcastEpisodeIcon; } else if (static_cast(item)->downloadProgress()>=0) { return Icon("go-down"); } else if (MusicLibraryItemPodcastEpisode::QueuedForDownload==static_cast(item)->downloadProgress()) { return Icon("clock"); } } break; case Qt::FontRole: if ((MusicLibraryItem::Type_Song==item->itemType() && item->parentItem() && MusicLibraryItem::Type_Podcast==item->parentItem()->itemType() && !static_cast(item)->song().hasBeenPlayed()) || (MusicLibraryItem::Type_Podcast==item->itemType() && static_cast(item)->unplayedEpisodes()>0)) { QFont f; f.setBold(true); return f; } break; case Cantata::Role_MainText: case Qt::DisplayRole: if (MusicLibraryItem::Type_Podcast==item->itemType() && static_cast(item)->unplayedEpisodes()) { return i18nc("podcast name (num unplayed episodes)", "%1 (%2)", item->data(), static_cast(item)->unplayedEpisodes()); } default: break; } return MultiMusicModel::data(index, role); } void OnlineServicesModel::clear() { QSet names; foreach (MusicLibraryItemRoot *col, collections) { names.insert(col->id()); } foreach (const QString &n, names) { removeService(n); } collections.clear(); } void OnlineServicesModel::setEnabled(bool e) { if (e==enabled) { return; } if (e) { connect(Covers::self(), SIGNAL(loaded(Song,int)), this, SLOT(coverLoaded(Song,int))); } else { disconnect(Covers::self(), SIGNAL(loaded(Song,int)), this, SLOT(coverLoaded(Song,int))); } enabled=e; if (!enabled) { stop(); } } void OnlineServicesModel::stop() { foreach (MusicLibraryItemRoot *col, collections) { static_cast(col)->stopLoader(); } } OnlineService * OnlineServicesModel::service(const QString &name) { int idx=name.isEmpty() ? -1 : indexOf(name); if (idx>=0) { return static_cast(collections.at(idx)); } foreach (OnlineService *srv, hiddenServices) { if (srv->id()==name) { return srv; } } return 0; } void OnlineServicesModel::setBusy(const QString &id, bool b) { int before=busyServices.count(); if (b) { busyServices.insert(id); } else { busyServices.remove(id); } if (before!=busyServices.count()) { emit busy(busyServices.count()); } } Qt::ItemFlags OnlineServicesModel::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled; } return Qt::NoItemFlags; } OnlineService * OnlineServicesModel::addService(const QString &name, const QSet &hidden) { OnlineService *srv=0; if (!service(name)) { if (name==JamendoService::constName) { srv=new JamendoService(this); } else if (name==MagnatuneService::constName) { srv=new MagnatuneService(this); } else if (name==SoundCloudService::constName) { srv=new SoundCloudService(this); } else if (name==PodcastService::constName) { srv=podcast=new PodcastService(this); } if (srv) { srv->loadConfig(); if (hidden.contains(srv->id())) { hiddenServices.append(srv); } else { beginInsertRows(QModelIndex(), collections.count(), collections.count()); collections.append(srv); endInsertRows(); } connect(srv, SIGNAL(error(const QString &)), SIGNAL(error(const QString &))); } } return srv; } void OnlineServicesModel::removeService(const QString &name) { int idx=indexOf(name); if (idx<0) { return; } OnlineService *srv=static_cast(collections.at(idx)); if (srv) { beginRemoveRows(QModelIndex(), idx, idx); collections.takeAt(idx); endRemoveRows(); MultiMusicModel::updateGenres(); // Destroy will stop service, and delete it (via deleteLater()) srv->destroy(); } } void OnlineServicesModel::stateChanged(const QString &name, bool state) { if (!state) { int idx=indexOf(name); if (idx<0) { return; } MultiMusicModel::updateGenres(); } } void OnlineServicesModel::load() { QSet hidden=Settings::self()->hiddenOnlineProviders().toSet(); addService(JamendoService::constName, hidden); addService(MagnatuneService::constName, hidden); addService(SoundCloudService::constName, hidden); addService(PodcastService::constName, hidden); } void OnlineServicesModel::setSearch(const QString &serviceName, const QString &text) { OnlineService *srv=service(serviceName); if (srv) { srv->setSearch(text); } } QMimeData * OnlineServicesModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData=0; QStringList paths=filenames(indexes); if (!paths.isEmpty()) { mimeData=new QMimeData(); PlayQueueModel::encode(*mimeData, PlayQueueModel::constUriMimeType, paths); } return mimeData; } #ifdef ENABLE_DEVICES_SUPPORT Device * OnlineServicesModel::device(const QString &udi) { if (!dev) { dev=new OnlineDevice(); } dev->setData(udi.mid(constUdiPrefix.length())); return dev; } #endif bool OnlineServicesModel::subscribePodcast(const QUrl &url) { if (podcast && !podcast->subscribedToUrl(url)) { podcast->addUrl(url); return true; } return false; } void OnlineServicesModel::downloadPodcasts(MusicLibraryItemPodcast *pod, const QList &episodes) { podcast->downloadPodcasts(pod, episodes); } void OnlineServicesModel::deleteDownloadedPodcasts(MusicLibraryItemPodcast *pod, const QList &episodes) { podcast->deleteDownloadedPodcasts(pod, episodes); } void OnlineServicesModel::setPodcastsAsListened(MusicLibraryItemPodcast *pod, const QList &episodes, bool listened) { podcast->setPodcastsAsListened(pod, episodes, listened); } bool OnlineServicesModel::isDownloading() const { return podcast->isDownloading(); } void OnlineServicesModel::cancelAll() { podcast->cancelAllJobs(); } // Required due to icon missing for StdActions::searchAction for Unity... See note in constructor above. void OnlineServicesModel::tooltipUpdated(QAction *act) { #if defined ENABLE_STREAMS || !defined UNITY_MENU_HACK Q_UNUSED(act) #else if (act!=searchAction && act==StdActions::self()->searchAction) { searchAction->setToolTip(StdActions::self()->searchAction->toolTip()); } #endif } void OnlineServicesModel::coverLoaded(const Song &song, int size) { Q_UNUSED(size) if (song.isArtistImageRequest() || !song.isFromOnlineService() || song.isComposerImageRequest()) { return; } QString id=song.onlineService(); OnlineService *srv=service(id); if (srv) { if (PodcastService::constName==id) { MusicLibraryItemPodcast *pod = static_cast(srv)->getPodcast(QUrl(song.file)); if (pod) { QModelIndex idx=index(pod->row(), 0, index(srv->row(), 0, QModelIndex())); emit dataChanged(idx, idx); } } else { MusicLibraryItemArtist *artistItem = srv->artist(song, false); if (artistItem) { MusicLibraryItemAlbum *albumItem = artistItem->album(song, false); if (albumItem) { QModelIndex idx=index(albumItem->row(), 0, index(artistItem->row(), 0, QModelIndex())); emit dataChanged(idx, idx); } } } } } void OnlineServicesModel::save() { QStringList disabled; foreach (OnlineService *i, hiddenServices) { disabled.append(i->id()); } disabled.sort(); Settings::self()->saveHiddenOnlineProviders(disabled); } QList OnlineServicesModel::getProviders() const { QList providers; foreach (OnlineService *i, hiddenServices) { providers.append(Provider(i->data(), static_cast(i)->icon(), static_cast(i)->id(), true, static_cast(i)->canConfigure())); } foreach (MusicLibraryItemRoot *i, collections) { providers.append(Provider(i->data(), static_cast(i)->icon(), static_cast(i)->id(), false, static_cast(i)->canConfigure())); } return providers; } void OnlineServicesModel::setHiddenProviders(const QSet &prov) { // bool added=false; bool changed=false; foreach (OnlineService *i, hiddenServices) { if (!prov.contains(i->id())) { beginInsertRows(QModelIndex(), collections.count(), collections.count()); collections.append(i); hiddenServices.removeAll(i); endInsertRows(); /*added=*/changed=true; } } foreach (MusicLibraryItemRoot *i, collections) { if (prov.contains(static_cast(i)->id())) { int r=row(i); if (r>=0) { beginRemoveRows(QModelIndex(), r, r); hiddenServices.append(static_cast(collections.takeAt(r))); endRemoveRows(); changed=true; } } } // if (added && collections.count()>1) { // emit needToSort(); // } updateGenres(); if (changed) { emit providersChanged(); } }