/* * Strawberry Music Player * This file was part of Clementine. * Copyright 2012-2013, David Sansome * Copyright 2012, 2014, John Maguire * Copyright 2013-2014, Krzysztof Sobiecki * Copyright 2014, Simeon Bird * Copyright 2019-2021, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Strawberry 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 Strawberry. If not, see . * */ #include #include "podcastservice.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core/application.h" #include "core/logging.h" #include "core/mergedproxymodel.h" #include "core/iconloader.h" #include "core/standarditemiconloader.h" //#include "podcastsmodel.h" #include "podcastservicemodel.h" #include "collection/collectionview.h" #include "opmlcontainer.h" #include "podcastbackend.h" #include "podcastdeleter.h" #include "podcastdownloader.h" #include "podcastinfodialog.h" #include "podcastupdater.h" #include "addpodcastdialog.h" #include "organize/organizedialog.h" #include "organize/organizeerrordialog.h" #include "playlist/playlistmanager.h" #include "device/devicemanager.h" #include "device/devicestatefiltermodel.h" #include "device/deviceview.h" const char* PodcastService::kServiceName = "Podcasts"; const char *PodcastService::kSettingsGroup = "Podcasts"; class PodcastSortProxyModel : public QSortFilterProxyModel { public: explicit PodcastSortProxyModel(QObject *parent = nullptr); protected: bool lessThan(const QModelIndex &left, const QModelIndex &right) const; }; PodcastService::PodcastService(Application *app, QObject *parent) : InternetService(Song::Source_Unknown, kServiceName, QString(), QString(), SettingsDialog::Page_Appearance, app, parent), use_pretty_covers_(true), hide_listened_(false), show_episodes_(0), icon_loader_(new StandardItemIconLoader(app->album_cover_loader(), this)), backend_(app->podcast_backend()), model_(new PodcastServiceModel(this)), proxy_(new PodcastSortProxyModel(this)), root_(nullptr), organize_dialog_(new OrganizeDialog(app_->task_manager())) { icon_loader_->SetModel(model_); proxy_->setSourceModel(model_); proxy_->setDynamicSortFilter(true); proxy_->sort(0); QObject::connect(backend_, &PodcastBackend::SubscriptionAdded, this, &PodcastService::SubscriptionAdded); QObject::connect(backend_, &PodcastBackend::SubscriptionRemoved, this, &PodcastService::SubscriptionRemoved); QObject::connect(backend_, &PodcastBackend::EpisodesAdded, this, &PodcastService::EpisodesAdded); QObject::connect(backend_, &PodcastBackend::EpisodesUpdated, this, &PodcastService::EpisodesUpdated); QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &PodcastService::CurrentSongChanged); QObject::connect(organize_dialog_.get(), &OrganizeDialog::FileCopied, this, &PodcastService::FileCopied); } PodcastService::~PodcastService() {} PodcastSortProxyModel::PodcastSortProxyModel(QObject *parent) : QSortFilterProxyModel(parent) {} bool PodcastSortProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { Q_UNUSED(left) Q_UNUSED(right) #if 0 const int left_type = left.data(InternetModel::Role_Type).toInt(); const int right_type = right.data(InternetModel::Role_Type).toInt(); // The special Add Podcast item comes first if (left_type == PodcastService::Type_AddPodcast) return true; else if (right_type == PodcastService::Type_AddPodcast) return false; // Otherwise we only compare identical typed items. if (left_type != right_type) return QSortFilterProxyModel::lessThan(left, right); switch (left_type) { case PodcastService::Type_Podcast: return left.data().toString().localeAwareCompare(right.data().toString()) < 0; case PodcastService::Type_Episode: { const PodcastEpisode left_episode = left.data(PodcastService::Role_Episode).value(); const PodcastEpisode right_episode = right.data(PodcastService::Role_Episode).value(); return left_episode.publication_date() > right_episode.publication_date(); } default: return QSortFilterProxyModel::lessThan(left, right); } #endif return false; } QStandardItem *PodcastService::CreateRootItem() { #if 0 root_ = new QStandardItem(IconLoader::Load("podcast"), tr("Podcasts")); root_->setData(true, InternetModel::Role_CanLazyLoad); return root_; #endif return nullptr; } void PodcastService::CopyToDevice() { if (selected_episodes_.isEmpty() && explicitly_selected_podcasts_.isEmpty()) { CopyToDevice(backend_->GetNewDownloadedEpisodes()); } else { CopyToDevice(selected_episodes_, explicitly_selected_podcasts_); } } void PodcastService::CopyToDevice(const PodcastEpisodeList &episodes_list) { SongList songs; Podcast podcast; for (const PodcastEpisode &episode : episodes_list) { podcast = backend_->GetSubscriptionById(episode.podcast_database_id()); songs.append(episode.ToSong(podcast)); } if (songs.isEmpty()) return; organize_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true); organize_dialog_->SetCopy(true); if (organize_dialog_->SetSongs(songs)) organize_dialog_->show(); } void PodcastService::CopyToDevice(const QModelIndexList &episode_indexes, const QModelIndexList &podcast_indexes) { PodcastEpisodeList episodes; for (const QModelIndex &idx : episode_indexes) { PodcastEpisode episode_tmp = idx.data(Role_Episode).value(); if (episode_tmp.downloaded()) episodes << episode_tmp; } for (const QModelIndex &idx : podcast_indexes) { for (int i = 0; i < idx.model()->rowCount(idx); ++i) { const QModelIndex &idx2 = idx.model()->index(i, 0, idx); PodcastEpisode episode_tmp = idx2.data(Role_Episode).value(); if (episode_tmp.downloaded() && !episode_tmp.listened()) episodes << episode_tmp; } } SongList songs; for (const PodcastEpisode &episode : episodes) { Podcast podcast = backend_->GetSubscriptionById(episode.podcast_database_id()); songs.append(episode.ToSong(podcast)); } organize_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true); organize_dialog_->SetCopy(true); if (organize_dialog_->SetSongs(songs)) organize_dialog_->show(); } void PodcastService::CancelDownload() { CancelDownload(selected_episodes_, explicitly_selected_podcasts_); } void PodcastService::CancelDownload(const QModelIndexList &episode_indexes, const QModelIndexList &podcast_indexes) { PodcastEpisodeList episodes; for (const QModelIndex &idx : episode_indexes) { if (!idx.isValid()) continue; PodcastEpisode episode_tmp = idx.data(Role_Episode).value(); episodes << episode_tmp; } for (const QModelIndex &idx : podcast_indexes) { if (!idx.isValid()) continue; for (int i = 0; i < idx.model()->rowCount(idx); ++i) { const QModelIndex &idx2 = idx.model()->index(i, 0, idx); if (!idx2.isValid()) continue; PodcastEpisode episode_tmp = idx2.data(Role_Episode).value(); episodes << episode_tmp; } } episodes = app_->podcast_downloader()->EpisodesDownloading(episodes); app_->podcast_downloader()->cancelDownload(episodes); } void PodcastService::LazyPopulate(QStandardItem *parent) { Q_UNUSED(parent) #if 0 switch (parent->data(InternetModel::Role_Type).toInt()) { case InternetModel::Type_Service: PopulatePodcastList(model_->invisibleRootItem()); model()->merged_model()->AddSubModel(parent->index(), proxy_); break; } #endif } void PodcastService::PopulatePodcastList(QStandardItem *parent) { // Do this here since the downloader won't be created yet in the ctor. QObject::connect(app_->podcast_downloader(), &PodcastDownloader::ProgressChanged, this, &PodcastService::DownloadProgressChanged); if (default_icon_.isNull()) { default_icon_ = IconLoader::Load("podcast"); } PodcastList podcasts = backend_->GetAllSubscriptions(); for (const Podcast &podcast : podcasts) { parent->appendRow(CreatePodcastItem(podcast)); } } void PodcastService::ClearPodcastList(QStandardItem *parent) { parent->removeRows(0, parent->rowCount()); } void PodcastService::UpdatePodcastText(QStandardItem *item, const int unlistened_count) const { const Podcast podcast = item->data(Role_Podcast).value(); QString title = podcast.title().simplified(); QFont font; if (unlistened_count > 0) { // Add the number of new episodes after the title. title.append(QString(" (%1)").arg(unlistened_count)); // Set a bold font font.setBold(true); } item->setFont(font); item->setText(title); } void PodcastService::UpdateEpisodeText(QStandardItem *item, const PodcastDownload::State state, const int percent) { const PodcastEpisode episode = item->data(Role_Episode).value(); QString title = episode.title().simplified(); QString tooltip; QFont font; QIcon icon; // Unlistened episodes are bold if (!episode.listened()) { font.setBold(true); } // Downloaded episodes get an icon if (episode.downloaded()) { if (downloaded_icon_.isNull()) { downloaded_icon_ = IconLoader::Load("document-save"); } icon = downloaded_icon_; } // Queued or downloading episodes get icons, tooltips, and maybe a title. switch (state) { case PodcastDownload::Queued: if (queued_icon_.isNull()) { queued_icon_ = IconLoader::Load("user-away"); } icon = queued_icon_; tooltip = tr("Download queued"); break; case PodcastDownload::Downloading: if (downloading_icon_.isNull()) { downloading_icon_ = IconLoader::Load("go-down"); } icon = downloading_icon_; tooltip = tr("Downloading (%1%)...").arg(percent); title = QString("[ %1% ] %2").arg(QString::number(percent), episode.title()); break; case PodcastDownload::Finished: case PodcastDownload::NotDownloading: break; } item->setFont(font); item->setText(title); item->setIcon(icon); } void PodcastService::UpdatePodcastText(QStandardItem *item, const PodcastDownload::State state, const int percent) { const Podcast podcast = item->data(Role_Podcast).value(); QString tooltip; QIcon icon; // Queued or downloading podcasts get icons, tooltips, and maybe a title. switch (state) { case PodcastDownload::Queued: if (queued_icon_.isNull()) { queued_icon_ = IconLoader::Load("user-away"); } icon = queued_icon_; item->setIcon(icon); tooltip = tr("Download queued"); break; case PodcastDownload::Downloading: if (downloading_icon_.isNull()) { downloading_icon_ = IconLoader::Load("go-down"); } icon = downloading_icon_; item->setIcon(icon); tooltip = tr("Downloading (%1%)...").arg(percent); break; case PodcastDownload::Finished: case PodcastDownload::NotDownloading: if (podcast.ImageUrlSmall().isValid()) { icon_loader_->LoadIcon(podcast.ImageUrlSmall(), QUrl(), item); } else { item->setIcon(default_icon_); } break; } } QStandardItem *PodcastService::CreatePodcastItem(const Podcast &podcast) { QStandardItem *item = new QStandardItem; // Add the episodes in this podcast and gather aggregate stats. int unlistened_count = 0; qint64 number = 0; for (const PodcastEpisode &episode : backend_->GetEpisodes(podcast.database_id())) { if (!episode.listened()) { unlistened_count++; } if (episode.listened() && hide_listened_) { continue; } else { item->appendRow(CreatePodcastEpisodeItem(episode)); ++number; } if ((number >= show_episodes_) && (show_episodes_ != 0)) { break; } } item->setIcon(default_icon_); //item->setData(Type_Podcast, InternetModel::Role_Type); item->setData(QVariant::fromValue(podcast), Role_Podcast); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable); UpdatePodcastText(item, unlistened_count); // Load the podcast's image if it has one if (podcast.ImageUrlSmall().isValid()) { icon_loader_->LoadIcon(podcast.ImageUrlSmall(), QUrl(), item); } podcasts_by_database_id_[podcast.database_id()] = item; return item; } QStandardItem *PodcastService::CreatePodcastEpisodeItem(const PodcastEpisode &episode) { QStandardItem *item = new QStandardItem; item->setText(episode.title().simplified()); //item->setData(Type_Episode, InternetModel::Role_Type); item->setData(QVariant::fromValue(episode), Role_Episode); //item->setData(InternetModel::PlayBehaviour_UseSongLoader, InternetModel::Role_PlayBehaviour); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable); UpdateEpisodeText(item); episodes_by_database_id_[episode.database_id()] = item; return item; } void PodcastService::ShowContextMenu(const QPoint &global_pos) { if (!context_menu_) { context_menu_ = new QMenu; context_menu_->addAction(IconLoader::Load("list-add"), tr("Add podcast..."), this, &PodcastService::AddPodcast); context_menu_->addAction(IconLoader::Load("view-refresh"), tr("Update all podcasts"), app_->podcast_updater(), &PodcastUpdater::UpdateAllPodcastsNow); context_menu_->addSeparator(); //context_menu_->addActions(GetPlaylistActions()); context_menu_->addSeparator(); update_selected_action_ = context_menu_->addAction(IconLoader::Load("view-refresh"), tr("Update this podcast"), this, &PodcastService::UpdateSelectedPodcast); download_selected_action_ = context_menu_->addAction(IconLoader::Load("download"), "", this, &PodcastService::DownloadSelectedEpisode); info_selected_action_ = context_menu_->addAction(IconLoader::Load("about-info"), tr("Podcast information"), this, &PodcastService::PodcastInfo); delete_downloaded_action_ = context_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete downloaded data"), this, &PodcastService::DeleteDownloadedData); copy_to_device_ = context_menu_->addAction(IconLoader::Load("multimedia-player-ipod-mini-blue"), tr("Copy to device..."), this, QOverload<>::of(&PodcastService::CopyToDevice)); cancel_download_ = context_menu_->addAction(IconLoader::Load("cancel"), tr("Cancel download"), this, QOverload<>::of(&PodcastService::CancelDownload)); remove_selected_action_ = context_menu_->addAction(IconLoader::Load("list-remove"), tr("Unsubscribe"), this, QOverload<>::of(&PodcastService::RemoveSelectedPodcast)); context_menu_->addSeparator(); set_new_action_ = context_menu_->addAction(tr("Mark as new"), this, &PodcastService::SetNew); set_listened_action_ = context_menu_->addAction(tr("Mark as listened"), this, QOverload<>::of(&PodcastService::SetListened)); context_menu_->addSeparator(); context_menu_->addAction(IconLoader::Load("configure"), tr("Configure podcasts..."), this, &PodcastService::ShowConfig); copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0); connect(app_->device_manager()->connected_devices_model(), &DeviceStateFilterModel::IsEmptyChanged, copy_to_device_, &QAction::setDisabled); } selected_episodes_.clear(); selected_podcasts_.clear(); explicitly_selected_podcasts_.clear(); QSet podcast_ids; #if 0 for (const QModelIndex &index : model()->selected_indexes()) { switch (index.data(InternetModel::Role_Type).toInt()) { case Type_Podcast: { const int id = index.data(Role_Podcast).value().database_id(); if (!podcast_ids.contains(id)) { selected_podcasts_.append(index); explicitly_selected_podcasts_.append(index); podcast_ids.insert(id); } break; } case Type_Episode: { selected_episodes_.append(index); // Add the parent podcast as well. const QModelIndex parent = index.parent(); const int id = parent.data(Role_Podcast).value().database_id(); if (!podcast_ids.contains(id)) { selected_podcasts_.append(parent); podcast_ids.insert(id); } break; } } } #endif const bool episodes = !selected_episodes_.isEmpty(); const bool podcasts = !selected_podcasts_.isEmpty(); update_selected_action_->setEnabled(podcasts); remove_selected_action_->setEnabled(podcasts); set_new_action_->setEnabled(episodes || podcasts); set_listened_action_->setEnabled(episodes || podcasts); cancel_download_->setEnabled(episodes || podcasts); if (selected_episodes_.count() == 1) { const PodcastEpisode episode = selected_episodes_[0].data(Role_Episode).value(); const bool downloaded = episode.downloaded(); const bool listened = episode.listened(); download_selected_action_->setEnabled(!downloaded); delete_downloaded_action_->setEnabled(downloaded); if (explicitly_selected_podcasts_.isEmpty()) { set_new_action_->setEnabled(listened); set_listened_action_->setEnabled(!listened || !episode.listened_date().isValid()); } } else { download_selected_action_->setEnabled(episodes); delete_downloaded_action_->setEnabled(episodes); } if (selected_podcasts_.count() == 1) { if (selected_episodes_.count() == 1) { info_selected_action_->setText(tr("Episode information")); info_selected_action_->setEnabled(true); } else { info_selected_action_->setText(tr("Podcast information")); info_selected_action_->setEnabled(true); } } else { info_selected_action_->setText(tr("Podcast information")); info_selected_action_->setEnabled(false); } if (explicitly_selected_podcasts_.isEmpty() && selected_episodes_.isEmpty()) { PodcastEpisodeList epis = backend_->GetNewDownloadedEpisodes(); set_listened_action_->setEnabled(!epis.isEmpty()); } if (selected_episodes_.count() > 1) { download_selected_action_->setText( tr("Download %n episodes", "", selected_episodes_.count())); } else { download_selected_action_->setText(tr("Download this episode")); } //GetAppendToPlaylistAction()->setEnabled(episodes || podcasts); //GetReplacePlaylistAction()->setEnabled(episodes || podcasts); //GetOpenInNewPlaylistAction()->setEnabled(episodes || podcasts); context_menu_->popup(global_pos); } void PodcastService::UpdateSelectedPodcast() { for (const QModelIndex &index : selected_podcasts_) { app_->podcast_updater()->UpdatePodcastNow( index.data(Role_Podcast).value()); } } void PodcastService::RemoveSelectedPodcast() { for (const QModelIndex &index : selected_podcasts_) { backend_->Unsubscribe(index.data(Role_Podcast).value()); } } void PodcastService::ReloadSettings() { InitialLoadSettings(); ClearPodcastList(model_->invisibleRootItem()); PopulatePodcastList(model_->invisibleRootItem()); } void PodcastService::InitialLoadSettings() { QSettings s; s.beginGroup(CollectionSettingsPage::kSettingsGroup); use_pretty_covers_ = s.value("pretty_covers", true).toBool(); s.endGroup(); s.beginGroup(kSettingsGroup); hide_listened_ = s.value("hide_listened", false).toBool(); show_episodes_ = s.value("show_episodes", 0).toInt(); s.endGroup(); // TODO(notme): reload the podcast icons that are already loaded? } void PodcastService::EnsureAddPodcastDialogCreated() { add_podcast_dialog_.reset(new AddPodcastDialog(app_)); } void PodcastService::AddPodcast() { EnsureAddPodcastDialogCreated(); add_podcast_dialog_->show(); } void PodcastService::FileCopied(int database_id) { SetListened(PodcastEpisodeList() << backend_->GetEpisodeById(database_id), true); } void PodcastService::SubscriptionAdded(const Podcast &podcast) { // Ensure the root item is lazy loaded already LazyLoadRoot(); // The podcast might already be in the list - maybe the LazyLoadRoot() above // added it. QStandardItem *item = podcasts_by_database_id_[podcast.database_id()]; if (!item) { item = CreatePodcastItem(podcast); model_->appendRow(item); } //emit ScrollToIndex(MapToMergedModel(item->index())); } void PodcastService::SubscriptionRemoved(const Podcast &podcast) { QStandardItem *item = podcasts_by_database_id_.take(podcast.database_id()); if (item) { // Remove any episode ID -> item mappings for the episodes in this podcast. for (int i = 0; i < item->rowCount(); ++i) { QStandardItem *episode_item = item->child(i); const int episode_id = episode_item->data(Role_Episode).value().database_id(); episodes_by_database_id_.remove(episode_id); } // Remove this episode's row model_->removeRow(item->row()); } } void PodcastService::EpisodesAdded(const PodcastEpisodeList &episodes) { QSet seen_podcast_ids; for (const PodcastEpisode &episode : episodes) { const int database_id = episode.podcast_database_id(); QStandardItem *parent = podcasts_by_database_id_[database_id]; if (!parent) continue; parent->appendRow(CreatePodcastEpisodeItem(episode)); if (!seen_podcast_ids.contains(database_id)) { // Update the unlistened count text once for each podcast int unlistened_count = 0; for (const PodcastEpisode &i : backend_->GetEpisodes(database_id)) { if (!i.listened()) { ++unlistened_count; } } UpdatePodcastText(parent, unlistened_count); seen_podcast_ids.insert(database_id); } const Podcast podcast = parent->data(Role_Podcast).value(); ReloadPodcast(podcast); } } void PodcastService::EpisodesUpdated(const PodcastEpisodeList &episodes) { QSet seen_podcast_ids; QMap podcasts_map; for (const PodcastEpisode &episode : episodes) { const int podcast_database_id = episode.podcast_database_id(); QStandardItem *item = episodes_by_database_id_[episode.database_id()]; QStandardItem *parent = podcasts_by_database_id_[podcast_database_id]; if (!item || !parent) continue; // Update the episode data on the item, and update the item's text. item->setData(QVariant::fromValue(episode), Role_Episode); UpdateEpisodeText(item); // Update the parent podcast's text too. if (!seen_podcast_ids.contains(podcast_database_id)) { // Update the unlistened count text once for each podcast int unlistened_count = 0; for (const PodcastEpisode &i : backend_->GetEpisodes(podcast_database_id)) { if (!i.listened()) { ++unlistened_count; } } UpdatePodcastText(parent, unlistened_count); seen_podcast_ids.insert(podcast_database_id); } const Podcast podcast = parent->data(Role_Podcast).value(); podcasts_map[podcast.database_id()] = podcast; } QList podcast_values = podcasts_map.values(); for (const Podcast &podcast_tmp : podcast_values) { ReloadPodcast(podcast_tmp); } } void PodcastService::DownloadSelectedEpisode() { for (const QModelIndex &idx : selected_episodes_) { app_->podcast_downloader()->DownloadEpisode(idx.data(Role_Episode).value()); } } void PodcastService::PodcastInfo() { if (selected_podcasts_.isEmpty()) { // Should never happen. return; } const Podcast podcast = selected_podcasts_[0].data(Role_Podcast).value(); podcast_info_dialog_.reset(new PodcastInfoDialog(app_)); if (selected_episodes_.count() == 1) { const PodcastEpisode episode = selected_episodes_[0].data(Role_Episode).value(); podcast_info_dialog_->ShowEpisode(episode, podcast); } else { podcast_info_dialog_->ShowPodcast(podcast); } } void PodcastService::DeleteDownloadedData() { for (const QModelIndex &idx : selected_episodes_) { app_->podcast_deleter()->DeleteEpisode(idx.data(Role_Episode).value()); } } void PodcastService::DownloadProgressChanged(const PodcastEpisode &episode, const PodcastDownload::State state, const int percent) { QStandardItem *item = episodes_by_database_id_[episode.database_id()]; QStandardItem *item2 = podcasts_by_database_id_[episode.podcast_database_id()]; if (!item || !item2) return; UpdateEpisodeText(item, state, percent); UpdatePodcastText(item2, state, percent); } void PodcastService::ShowConfig() { //app_->OpenSettingsDialogAtPage(SettingsDialog::Page_Podcasts); } void PodcastService::CurrentSongChanged(const Song &metadata) { // This does two db queries, and we are called on every song change, so run this off the main thread. #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) (void)QtConcurrent::run(&PodcastService::UpdatePodcastListenedStateAsync, this, metadata); #else (void)QtConcurrent::run(this, &PodcastService::UpdatePodcastListenedStateAsync, metadata); #endif } void PodcastService::UpdatePodcastListenedStateAsync(const Song &metadata) { // Check whether this song is one of our podcast episodes. PodcastEpisode episode = backend_->GetEpisodeByUrlOrLocalUrl(metadata.url()); if (!episode.is_valid()) return; // Mark it as listened if it's not already if (!episode.listened() || !episode.listened_date().isValid()) { episode.set_listened(true); episode.set_listened_date(QDateTime::currentDateTime()); backend_->UpdateEpisodes(PodcastEpisodeList() << episode); } } void PodcastService::SetNew() { SetListened(selected_episodes_, explicitly_selected_podcasts_, false); } void PodcastService::SetListened() { if (selected_episodes_.isEmpty() && explicitly_selected_podcasts_.isEmpty()) { SetListened(backend_->GetNewDownloadedEpisodes(), true); } else { SetListened(selected_episodes_, explicitly_selected_podcasts_, true); } } void PodcastService::SetListened(const PodcastEpisodeList &episodes_list, const bool listened) { PodcastEpisodeList episodes; QDateTime current_date_time = QDateTime::currentDateTime(); for (PodcastEpisode episode : episodes_list) { episode.set_listened(listened); if (listened) { episode.set_listened_date(current_date_time); } episodes << episode; } backend_->UpdateEpisodes(episodes); } void PodcastService::SetListened(const QModelIndexList &episode_indexes, const QModelIndexList& podcast_indexes, const bool listened) { PodcastEpisodeList episodes; // Get all the episodes from the indexes. for (const QModelIndex& index : episode_indexes) { episodes << index.data(Role_Episode).value(); } for (const QModelIndex& podcast : podcast_indexes) { for (int i = 0; i < podcast.model()->rowCount(podcast); ++i) { const QModelIndex& index = podcast.model()->index(i, 0, podcast); episodes << index.data(Role_Episode).value(); } } // Update each one with the new state and maybe the listened time. QDateTime current_date_time = QDateTime::currentDateTime(); for (int i = 0; i < episodes.count(); ++i) { PodcastEpisode *episode = &episodes[i]; episode->set_listened(listened); if (listened) { episode->set_listened_date(current_date_time); } } backend_->UpdateEpisodes(episodes); } QModelIndex PodcastService::MapToMergedModel(const QModelIndex &idx) const { Q_UNUSED(idx) //return model()->merged_model()->mapFromSource(proxy_->mapFromSource(index)); return QModelIndex(); } void PodcastService::LazyLoadRoot() { #if 0 if (root_->data(InternetModel::Role_CanLazyLoad).toBool()) { root_->setData(false, InternetModel::Role_CanLazyLoad); LazyPopulate(root_); } #endif } void PodcastService::SubscribeAndShow(const QVariant &podcast_or_opml) { if (podcast_or_opml.canConvert()) { Podcast podcast(podcast_or_opml.value()); backend_->Subscribe(&podcast); // Lazy load the root item if it hasn't been already LazyLoadRoot(); QStandardItem *item = podcasts_by_database_id_[podcast.database_id()]; if (item) { // There will be an item already if this podcast was already there, otherwise it'll be scrolled to when the item is created. //emit ScrollToIndex(MapToMergedModel(item->index())); } } else if (podcast_or_opml.canConvert()) { EnsureAddPodcastDialogCreated(); add_podcast_dialog_->ShowWithOpml(podcast_or_opml.value()); } } void PodcastService::ReloadPodcast(const Podcast &podcast) { if (!(hide_listened_ || (show_episodes_ > 0))) { return; } QStandardItem *item = podcasts_by_database_id_[podcast.database_id()]; model_->invisibleRootItem()->removeRow(item->row()); model_->invisibleRootItem()->appendRow(CreatePodcastItem(podcast)); }