diff --git a/src/services/gpodder/GpodderProvider.cpp b/src/services/gpodder/GpodderProvider.cpp index 925c354c9e..3da37288cc 100644 --- a/src/services/gpodder/GpodderProvider.cpp +++ b/src/services/gpodder/GpodderProvider.cpp @@ -1,1297 +1,1297 @@ /**************************************************************************************** * Copyright (c) 2011 Stefan Derkits * * Copyright (c) 2011 Christian Wagner * * Copyright (c) 2011 Felix Winter * * Copyright (c) 2011 Lucas Lira Gomes * * * * 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 . * ****************************************************************************************/ #define DEBUG_PREFIX "GpodderProvider" #include "GpodderProvider.h" #include "core-impl/capabilities/timecode/TimecodeWriteCapability.h" #include "core-impl/podcasts/sql/SqlPodcastProvider.h" #include "core/logger/Logger.h" #include "core/support/Amarok.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "EngineController.h" #include "gpodder/GpodderServiceConfig.h" #include "NetworkAccessManagerProxy.h" #include "PodcastModel.h" #include #include #include #include #include using namespace Podcasts; GpodderProvider::GpodderProvider( const QString& username, const QString& devicename, ApiRequest *apiRequest ) : m_apiRequest( apiRequest ) , m_username( username ) , m_deviceName( devicename ) , m_channels() , m_addRemoveResult() , m_deviceUpdatesResult() , m_episodeActionListResult() , m_timestampStatus( 0 ) , m_timestampSubscription( subscriptionTimestamp() ) - , m_removeAction( 0 ) + , m_removeAction( nullptr ) , m_addList() , m_removeList() , m_timerGeneratePlayAction( new QTimer( this ) ) , m_timerSynchronizeStatus( new QTimer( this ) ) , m_timerSynchronizeSubscriptions( new QTimer( this ) ) { //We have to load episode actions and podcasts subscriptions changes //that weren't uploaded before the time we closed amarok loadCachedEpisodeActions(); loadCachedPodcastsChanges(); //Request all channels and episodes from m_devicename device and after it //request episode actions too requestDeviceUpdates(); //Connect default podcasts signals to make possible to ask the user if he wants //to upload a new local podcast to gpodder.net connect( The::playlistManager()->defaultPodcasts(), &PodcastProvider::playlistAdded, this, &GpodderProvider::slotSyncPlaylistAdded ); connect( The::playlistManager()->defaultPodcasts(), &PodcastProvider::playlistRemoved, this, &GpodderProvider::slotSyncPlaylistRemoved ); Podcasts::SqlPodcastProvider *sqlPodcastProvider; sqlPodcastProvider = dynamic_cast ( The::playlistManager()->defaultPodcasts() ); connect( The::podcastModel(), &PlaylistBrowserNS::PodcastModel::episodeMarkedAsNew, this, &GpodderProvider::slotEpisodeMarkedAsNew ); if( sqlPodcastProvider ) { connect( sqlPodcastProvider, &SqlPodcastProvider::episodeDeleted, this, &GpodderProvider::slotEpisodeDeleted ); connect( sqlPodcastProvider,&SqlPodcastProvider::episodeDownloaded, this, &GpodderProvider::slotEpisodeDownloaded ); } //Connect engine controller signals to make possible to synchronize podcast status connect( The::engineController(), &EngineController::trackChanged, this, &GpodderProvider::slotTrackChanged ); connect( The::engineController(), &EngineController::trackPositionChanged, this, &GpodderProvider::slotTrackPositionChanged ); connect( The::engineController(), &EngineController::paused, this, &GpodderProvider::slotPaused ); //These timers will periodically synchronize data between local podcasts and gpodder.net connect( m_timerSynchronizeStatus, &QTimer::timeout, this, &GpodderProvider::timerSynchronizeStatus ); connect( m_timerSynchronizeSubscriptions, &QTimer::timeout, this, &GpodderProvider::timerSynchronizeSubscriptions ); connect( m_timerGeneratePlayAction, &QTimer::timeout, this, &GpodderProvider::timerGenerateEpisodeAction ); m_timerGeneratePlayAction->stop(); m_timerSynchronizeStatus->stop(); m_timerSynchronizeSubscriptions->stop(); } GpodderProvider::~GpodderProvider() { delete m_timerGeneratePlayAction; delete m_timerSynchronizeStatus; delete m_timerSynchronizeSubscriptions; //Save cached episode actions and Podcast changes, in order to //upload them to gpodder.net in the next saveCachedEpisodeActions(); saveCachedPodcastsChanges(); m_uploadEpisodeStatusMap.clear(); m_episodeStatusMap.clear(); m_redirectionUrlMap.clear(); m_channels.clear(); } bool GpodderProvider::possiblyContainsTrack( const QUrl &url ) const { DEBUG_BLOCK foreach( PodcastChannelPtr ptr, m_channels ) { foreach( PodcastEpisodePtr episode, ptr->episodes() ) { if( episode->uidUrl() == url.url() ) return true; } } return false; } Meta::TrackPtr GpodderProvider::trackForUrl( const QUrl &url ) { DEBUG_BLOCK if( url.isEmpty() ) return Meta::TrackPtr(); foreach( PodcastChannelPtr podcast, m_channels ) { foreach( PodcastEpisodePtr episode, podcast->episodes() ) { if( episode->uidUrl() == url.url() ) { return Meta::TrackPtr::dynamicCast( episode ); } } } return Meta::TrackPtr(); } PodcastEpisodePtr GpodderProvider::episodeForGuid( const QString &guid ) { foreach( PodcastChannelPtr ptr, m_channels ) { foreach( PodcastEpisodePtr episode, ptr->episodes() ) { if( episode->guid() == guid ) return episode; } } return PodcastEpisodePtr(); } void GpodderProvider::addPodcast( const QUrl &url ) { Q_UNUSED( url ) } Playlists::PlaylistPtr GpodderProvider::addPlaylist( Playlists::PlaylistPtr playlist ) { DEBUG_BLOCK PodcastChannelPtr channel = PodcastChannelPtr::dynamicCast( playlist ); if( channel.isNull() ) return Playlists::PlaylistPtr(); //This function is executed every time a new channel is found on gpodder.net PodcastChannelPtr master; PodcastChannelPtr slave; foreach( PodcastChannelPtr tempChannel, The::playlistManager()->defaultPodcasts()->channels() ) if( tempChannel->url() == channel->url() ) master = tempChannel; foreach( PodcastChannelPtr tempChannel, this->channels() ) if( tempChannel->url() == channel->url() ) slave = tempChannel; if( !master ) master = The::playlistManager()->defaultPodcasts()->addChannel( channel ); if( !slave ) { slave = this->addChannel( master ); //If playlist is not a GpodderPodcastChannelPtr then we must subscribe //it in gpodder.net if( !GpodderPodcastChannelPtr::dynamicCast( playlist ) ) { //The service will try to subscribe this podcast in gpodder.net in //the next synchronization QUrl url = QUrl( slave->url().url() ); m_removeList.removeAll( url ); m_addList << url; } } //Create a playlist synchronization between master and slave The::playlistManager()->setupSync( Playlists::PlaylistPtr::dynamicCast( master ), Playlists::PlaylistPtr::dynamicCast( slave ) ); return Playlists::PlaylistPtr::dynamicCast( slave ); } PodcastChannelPtr GpodderProvider::addChannel( const PodcastChannelPtr &channel ) { DEBUG_BLOCK GpodderPodcastChannelPtr gpodderChannel( new GpodderPodcastChannel( this, channel ) ); m_channels << PodcastChannelPtr::dynamicCast( gpodderChannel );; emit playlistAdded( Playlists::PlaylistPtr::dynamicCast( gpodderChannel ) ); return PodcastChannelPtr::dynamicCast( gpodderChannel ); } PodcastEpisodePtr GpodderProvider::addEpisode( PodcastEpisodePtr episode ) { if( episode.isNull() ) return PodcastEpisodePtr(); if( episode->channel().isNull() ) { debug() << "channel is null"; return PodcastEpisodePtr(); } return episode; } PodcastChannelList GpodderProvider::channels() { DEBUG_BLOCK PodcastChannelList list; foreach( PodcastChannelPtr channel, m_channels ) list << PodcastChannelPtr::dynamicCast( channel ); return list; } QString GpodderProvider::prettyName() const { return i18n( "Gpodder Podcasts" ); } QIcon GpodderProvider::icon() const { return QIcon::fromTheme( "view-services-gpodder-amarok" ); } Playlists::PlaylistList GpodderProvider::playlists() { Playlists::PlaylistList playlists; foreach( PodcastChannelPtr channel, m_channels ) playlists << Playlists::PlaylistPtr::staticCast( channel ); return playlists; } void GpodderProvider::completePodcastDownloads() { } void GpodderProvider::removeChannel( const QUrl &url ) { for( int i = 0; i < m_channels.size(); i++ ) { if( m_channels.at( i )->url() == url ) { PodcastChannelPtr channel = m_channels.at( i ); QUrl url = QUrl( channel->url().url() ); m_channels.removeAll( channel ); m_episodeStatusMap.remove( url ); m_uploadEpisodeStatusMap.remove( url ); m_addList.removeAll( url ); emit playlistRemoved( Playlists::PlaylistPtr::dynamicCast( channel ) ); return; } } } QActionList GpodderProvider::channelActions( PodcastChannelList channels ) { QActionList actions; if( channels.isEmpty() ) return actions; if( m_removeAction == 0 ) { m_removeAction = new QAction( QIcon::fromTheme( "edit-delete" ), i18n( "&Delete Channel and Episodes" ), this ); m_removeAction->setProperty( "popupdropper_svg_id", "delete" ); connect( m_removeAction, SIGNAL(triggered()), SLOT(slotRemoveChannels()) ); } //Set the episode list as data that we'll retrieve in the slot m_removeAction->setData( QVariant::fromValue( channels ) ); actions << m_removeAction; return actions; } QActionList GpodderProvider::playlistActions( const Playlists::PlaylistList &playlists ) { PodcastChannelList channels; foreach( const Playlists::PlaylistPtr &playlist, playlists ) { PodcastChannelPtr channel = PodcastChannelPtr::dynamicCast( playlist ); if( channel ) channels << channel; } return channelActions( channels ); } void GpodderProvider::slotRemoveChannels() { DEBUG_BLOCK QAction *action = qobject_cast( QObject::sender() ); if( action == 0 ) return; PodcastChannelList channels = action->data().value(); action->setData( QVariant() ); //Clear data foreach( PodcastChannelPtr channel, channels ) { removeChannel( channel->url() ); //The service will try to unsubscribe this podcast from gpodder.net //in the next synchronization m_removeList << channel->url(); } } void GpodderProvider::slotSyncPlaylistAdded( Playlists::PlaylistPtr playlist ) { PodcastChannelPtr channel = Podcasts::PodcastChannelPtr::dynamicCast( playlist ); //If the new channel already exist in gpodder channels, then //we don't have to add it to gpodder.net again foreach( PodcastChannelPtr tempChannel, m_channels ) if( channel->url() == tempChannel->url() ) return; addPlaylist( playlist ); m_timerSynchronizeSubscriptions->start( 60000 ); } void GpodderProvider::slotSyncPlaylistRemoved( Playlists::PlaylistPtr playlist ) { Podcasts::PodcastChannelPtr channel = Podcasts::PodcastChannelPtr::dynamicCast( playlist ); //If gpodder channels doesn't contains the removed channel from default //podcast provider, then we don't have to remove it from gpodder.net foreach( PodcastChannelPtr tempChannel, m_channels ) if( channel->url() == tempChannel->url() ) { removeChannel( tempChannel->url() ); //The service will try to unsubscribe this podcast from gpodder.net //in the next synchronization m_removeList << tempChannel->url(); m_timerSynchronizeSubscriptions->start( 60000 ); return; } } qulonglong GpodderProvider::subscriptionTimestamp() { KConfigGroup config = Amarok::config( GpodderServiceConfig::configSectionName() ); return config.readEntry( "subscriptionTimestamp", 0 ); } void GpodderProvider::setSubscriptionTimestamp( qulonglong newTimestamp ) { KConfigGroup config = Amarok::config( GpodderServiceConfig::configSectionName() ); config.writeEntry( "subscriptionTimestamp", newTimestamp ); } void GpodderProvider::synchronizeStatus() { DEBUG_BLOCK debug() << "new episodes status: " << m_uploadEpisodeStatusMap.size(); if( !QNetworkConfigurationManager().isOnline() ) return; if( !m_uploadEpisodeStatusMap.isEmpty() ) { m_episodeActionsResult = m_apiRequest->uploadEpisodeActions( m_username, m_uploadEpisodeStatusMap.values() ); //Only clear m_episodeStatusList if the synchronization with gpodder.net really worked connect( m_episodeActionsResult.data(), SIGNAL(finished()), SLOT(slotSuccessfulStatusSynchronisation()) ); connect( m_episodeActionsResult.data(), SIGNAL(requestError(QNetworkReply::NetworkError)), SLOT(synchronizeStatusRequestError(QNetworkReply::NetworkError)) ); connect( m_episodeActionsResult.data(), SIGNAL(parseError()), SLOT(synchronizeStatusParseError()) ); Amarok::Logger::shortMessage( i18n( "Trying to synchronize statuses with gpodder.net" ) ); } else m_timerSynchronizeStatus->stop(); } void GpodderProvider::slotSuccessfulStatusSynchronisation() { DEBUG_BLOCK m_timestampStatus = QDateTime::currentMSecsSinceEpoch(); m_uploadEpisodeStatusMap.clear(); //In addition, the server MUST send any URLs that have been rewritten (sanitized, see bug:747) //as a list of tuples with the key "update_urls". The client SHOULD parse this list and update //the local subscription list accordingly (the server only sanitizes the URL, so the semantic //"content" should stay the same and therefore the client can simply update the URL value //locally and use it for future updates updateLocalPodcasts( m_episodeActionsResult->updateUrlsList() ); } void GpodderProvider::synchronizeStatusParseError() { DEBUG_BLOCK QTimer::singleShot( 20000, this, SLOT(timerSynchronizeStatus()) ); debug() << "synchronizeStatus [Status Synchronization] - Parse error"; } void GpodderProvider::synchronizeStatusRequestError(QNetworkReply::NetworkError error) { DEBUG_BLOCK QTimer::singleShot( 20000, this, SLOT(timerSynchronizeStatus()) ); debug() << "synchronizeStatus [Status Synchronization] - Request error nr.: " << error; } void GpodderProvider::synchronizeSubscriptions() { DEBUG_BLOCK debug() << "add: " << m_addList.size(); debug() << "remove: " << m_removeList.size(); if( !QNetworkConfigurationManager().isOnline() ) return; if( !m_removeList.isEmpty() || !m_addList.isEmpty() ) { m_addRemoveResult = m_apiRequest->addRemoveSubscriptions( m_username, m_deviceName, m_addList, m_removeList ); //Only clear m_addList and m_removeList if the synchronization with gpodder.net really worked connect( m_addRemoveResult.data(), SIGNAL(finished()), this, SLOT(slotSuccessfulSubscriptionSynchronisation()) ); Amarok::Logger::shortMessage( i18n( "Trying to synchronize subscriptions with gpodder.net" ) ); } else m_timerSynchronizeSubscriptions->stop(); } void GpodderProvider::slotSuccessfulSubscriptionSynchronisation() { DEBUG_BLOCK m_timestampSubscription = QDateTime::currentMSecsSinceEpoch(); setSubscriptionTimestamp( m_timestampSubscription ); m_addList.clear(); m_removeList.clear(); //In addition, the server MUST send any URLs that have been rewritten (sanitized, see bug:747) //as a list of tuples with the key "update_urls". The client SHOULD parse this list and update //the local subscription list accordingly (the server only sanitizes the URL, so the semantic //"content" should stay the same and therefore the client can simply update the URL value //locally and use it for future updates updateLocalPodcasts( m_addRemoveResult->updateUrlsList() ); } void GpodderProvider::slotTrackChanged( Meta::TrackPtr track ) { m_trackToSyncStatus = nullptr; if( track != Meta::TrackPtr( 0 ) ) { //If the episode is from one of the gpodder subscribed podcasts, then we must keep looking it if( ( this->possiblyContainsTrack( QUrl( track->uidUrl() ) ) ) ) { m_trackToSyncStatus = track; QTimer::singleShot( 10000, this, SLOT(timerPrepareToSyncPodcastStatus()) ); //A bookmark will be created if we have a play status available, //for current track, at m_episodeStatusMap createPlayStatusBookmark(); return; } } m_timerGeneratePlayAction->stop(); //EpisodeActions should be sent when the user clicks //stops and doesn't resume listening in e.g. 1 minute //Or when the user is not listening a podcast in e.g. 1 minute m_timerSynchronizeStatus->start( 60000 ); } void GpodderProvider::slotTrackPositionChanged( qint64 position, bool userSeek ) { Q_UNUSED( position ) //If the current track is in one of the subscribed gpodder channels and it's position //is not at the beginning of the track, then we probably should sync it status. if( m_trackToSyncStatus ) { if( userSeek ) { //Test if this track still playing after 10 seconds to avoid accidentally user changes QTimer::singleShot( 10000, this, SLOT(timerPrepareToSyncPodcastStatus()) ); } } } void GpodderProvider::slotPaused() { m_timerGeneratePlayAction->stop(); //EpisodeActions should be sent when the user clicks pause //or stop and doesn't resume listening in e.g. 1 minute m_timerSynchronizeStatus->start( 60000 ); } void GpodderProvider::timerSynchronizeSubscriptions() { synchronizeSubscriptions(); } void GpodderProvider::timerSynchronizeStatus() { synchronizeStatus(); } void GpodderProvider::timerPrepareToSyncPodcastStatus() { if( The::engineController()->currentTrack() == m_trackToSyncStatus ) { EpisodeActionPtr tempEpisodeAction; PodcastEpisodePtr tempEpisode = PodcastEpisodePtr::dynamicCast( m_trackToSyncStatus ); if( tempEpisode ) { qulonglong positionSeconds = The::engineController()->trackPosition(); qulonglong lengthSeconds = The::engineController()->trackLength() / 1000; QString podcastUrl = resolvedPodcastUrl( tempEpisode ).url(); tempEpisodeAction = EpisodeActionPtr( new EpisodeAction( QUrl( podcastUrl ), QUrl( tempEpisode->uidUrl() ), m_deviceName, EpisodeAction::Play, QDateTime::currentMSecsSinceEpoch(), 1, positionSeconds + 1, lengthSeconds ) ); //Any previous episodeAction, from the same podcast, will be replaced m_uploadEpisodeStatusMap.insert( QUrl( tempEpisode->uidUrl() ), tempEpisodeAction ); } //Starts to generate EpisodeActions m_timerGeneratePlayAction->start( 30000 ); } } void GpodderProvider::timerGenerateEpisodeAction() { //Create and update episode actions if( The::engineController()->currentTrack() == m_trackToSyncStatus ) { EpisodeActionPtr tempEpisodeAction; PodcastEpisodePtr tempEpisode = PodcastEpisodePtr::dynamicCast( m_trackToSyncStatus ); if( tempEpisode ) { qulonglong positionSeconds = The::engineController()->trackPosition(); qulonglong lengthSeconds = The::engineController()->trackLength() / 1000; QString podcastUrl = resolvedPodcastUrl( tempEpisode ).url(); tempEpisodeAction = EpisodeActionPtr( new EpisodeAction( QUrl( podcastUrl ), QUrl( tempEpisode->uidUrl() ), m_deviceName, EpisodeAction::Play, QDateTime::currentMSecsSinceEpoch(), 1, positionSeconds + 1, lengthSeconds ) ); //Any previous episodeAction, from the same podcast, will be replaced m_uploadEpisodeStatusMap.insert( QUrl( tempEpisode->uidUrl() ), tempEpisodeAction ); //Make local podcasts aware of new episodeActions m_episodeStatusMap.insert( QUrl( tempEpisode->uidUrl() ), tempEpisodeAction ); } } } void GpodderProvider::requestDeviceUpdates() { DEBUG_BLOCK if( !QNetworkConfigurationManager().isOnline() ) { QTimer::singleShot( 10000, this, SLOT(requestDeviceUpdates()) ); return; } m_deviceUpdatesResult = m_apiRequest->deviceUpdates( m_username, m_deviceName, 0 ); connect( m_deviceUpdatesResult.data(), SIGNAL(finished()), SLOT(deviceUpdatesFinished()) ); connect( m_deviceUpdatesResult.data(), SIGNAL(requestError(QNetworkReply::NetworkError)), SLOT(deviceUpdatesRequestError(QNetworkReply::NetworkError)) ); connect( m_deviceUpdatesResult.data(), SIGNAL(parseError()), SLOT(deviceUpdatesParseError()) ); } void GpodderProvider::deviceUpdatesFinished() { DEBUG_BLOCK debug() << "DeviceUpdate timestamp: " << m_deviceUpdatesResult->timestamp(); //Channels to subscribe locally foreach( mygpo::PodcastPtr podcast, m_deviceUpdatesResult->addList() ) { debug() << "Subscribing GPO channel: " << podcast->title() << ": " << podcast->url(); GpodderPodcastChannelPtr channel = GpodderPodcastChannelPtr( new GpodderPodcastChannel( this, podcast ) ); //First we need to resolve redirection url's if there is any requestUrlResolve( channel ); } //Request the last episode status for every episode in gpodder.net //subscribed podcasts QTimer::singleShot( 1000, this, SLOT(requestEpisodeActionsInCascade()) ); //Only after all subscription changes are committed should we save the timestamp m_timestampSubscription = m_deviceUpdatesResult->timestamp(); setSubscriptionTimestamp( m_timestampSubscription ); } void GpodderProvider::continueDeviceUpdatesFinished() { foreach( GpodderPodcastChannelPtr channel, m_resolvedChannelsToBeAdded ) { m_channelsToRequestActions.enqueue( channel->url() ); PodcastChannelPtr master; PodcastChannelPtr slave; slave = this->addChannel( PodcastChannelPtr::dynamicCast( channel ) ); foreach( PodcastChannelPtr tempChannel, The::playlistManager()->defaultPodcasts()->channels() ) if( tempChannel->url() == channel->url() ) master = tempChannel; if( !master ) master = The::playlistManager()->defaultPodcasts()->addChannel( slave ); //Create a playlist synchronization between master and slave The::playlistManager()->setupSync( Playlists::PlaylistPtr::dynamicCast( master ), Playlists::PlaylistPtr::dynamicCast( slave ) ); } m_resolvedChannelsToBeAdded.clear(); } void GpodderProvider::deviceUpdatesParseError() { DEBUG_BLOCK QTimer::singleShot( 10000, this, SLOT(requestDeviceUpdates()) ); debug() << "deviceUpdates [Subscription Synchronization] - Parse error"; Amarok::Logger::shortMessage( i18n( "GPodder Service failed to get data from the server. Will retry in 10 seconds..." ) ); } void GpodderProvider::deviceUpdatesRequestError( QNetworkReply::NetworkError error ) { DEBUG_BLOCK QTimer::singleShot( 10000, this, SLOT(requestDeviceUpdates()) ); debug() << "deviceUpdates [Subscription Synchronization] - Request error nr.: " << error; Amarok::Logger::shortMessage( i18n( "GPodder Service failed to get data from the server. Will retry in 10 seconds..." ) ); } void GpodderProvider::requestEpisodeActionsInCascade() { DEBUG_BLOCK if( !QNetworkConfigurationManager().isOnline() ) { QTimer::singleShot( 10000, this, SLOT(requestEpisodeActionsInCascade()) ); return; } //This function will download all episode actions for //every podcast contained in m_channelsToRequestActions if( !m_channelsToRequestActions.isEmpty() ) { QUrl url = m_channelsToRequestActions.head(); m_episodeActionListResult = m_apiRequest->episodeActionsByPodcast( m_username, url.toString(), true ); debug() << "Requesting actions for " << url.toString(); connect( m_episodeActionListResult.data(), SIGNAL(finished()), SLOT(episodeActionsInCascadeFinished()) ); connect( m_episodeActionListResult.data(), SIGNAL(requestError(QNetworkReply::NetworkError)), SLOT(episodeActionsInCascadeRequestError(QNetworkReply::NetworkError)) ); connect( m_episodeActionListResult.data(), SIGNAL(parseError()), SLOT(episodeActionsInCascadeParseError()) ); } else { //We should try to upload cached EpisodeActions to gpodder.net synchronizeStatus(); } } void GpodderProvider::episodeActionsInCascadeFinished() { DEBUG_BLOCK m_timestampStatus = m_episodeActionListResult->timestamp(); foreach( EpisodeActionPtr tempEpisodeAction, m_episodeActionListResult->list() ) { if( tempEpisodeAction->action() == EpisodeAction::Play ) { debug() << QString( "Adding a new play status to episode: %1" ) .arg( tempEpisodeAction->episodeUrl().toString() ); m_episodeStatusMap.insert( tempEpisodeAction->episodeUrl(), tempEpisodeAction ); //A bookmark will be created if we have a play status available, //for current track, at m_episodeStatusMap createPlayStatusBookmark(); } else { PodcastChannelPtr channel; PodcastEpisodePtr episode; foreach( PodcastChannelPtr tempChannel, m_channels ) if( tempChannel->url() == tempEpisodeAction->podcastUrl() ) { channel = tempChannel; foreach( PodcastEpisodePtr tempEpisode, channel->episodes() ) if( tempEpisode->uidUrl() == tempEpisodeAction->episodeUrl().toString() ) episode = tempEpisode; } if( channel && episode ) { if( tempEpisodeAction->action() == EpisodeAction::New ) { if( !episode ) { debug() << QString( "New episode to be added found: %1" ) .arg( tempEpisodeAction->episodeUrl().toString() ); PodcastEpisodePtr tempEpisode; tempEpisode = PodcastEpisodePtr( new PodcastEpisode() ); tempEpisode->setUidUrl( tempEpisodeAction->episodeUrl() ); tempEpisode->setChannel( PodcastChannelPtr::dynamicCast( channel ) ); channel->addEpisode( tempEpisode ); } else { debug() << QString( "Marking an existent episode as new: %1" ) .arg( tempEpisodeAction->episodeUrl().toString() ); episode->setNew( true ); } } else if( tempEpisodeAction->action() == EpisodeAction::Download ) { debug() << QString( "Adding a new download status to episode: %1" ) .arg( tempEpisodeAction->episodeUrl().toString() ); } else if( tempEpisodeAction->action() == EpisodeAction::Delete ) { debug() << QString( "Adding a new delete status to episode: %1" ) .arg( tempEpisodeAction->episodeUrl().toString() ); } m_episodeStatusMap.insert( tempEpisodeAction->episodeUrl(), tempEpisodeAction ); } else { //For some reason the podcast and/or episode for this action //wasn't found debug() << QString( "Episode and/or channel not found" );; } } } //We must remove this podcast url and continue with the others m_channelsToRequestActions.dequeue(); QTimer::singleShot( 100, this, SLOT(requestEpisodeActionsInCascade()) ); } void GpodderProvider::episodeActionsInCascadeParseError() { DEBUG_BLOCK QTimer::singleShot( 10000, this, SLOT(requestEpisodeActionsInCascade()) ); //If we fail to get EpisodeActions for this channel then we must put it //at the end of the list. In order to be synced later on. m_channelsToRequestActions.enqueue( m_channelsToRequestActions.dequeue() ); debug() << "episodeActionsInCascade [Status Synchronization] - Parse Error"; } void GpodderProvider::episodeActionsInCascadeRequestError( QNetworkReply::NetworkError error ) { DEBUG_BLOCK QTimer::singleShot( 10000, this, SLOT(requestEpisodeActionsInCascade()) ); //If we fail to get EpisodeActions for this channel then we must put it //at the end of the list. In order to be synced later on. m_channelsToRequestActions.enqueue( m_channelsToRequestActions.dequeue() ); debug() << "episodeActionsInCascade [Status Synchronization] - Request error nr.: " << error; } void GpodderProvider::updateLocalPodcasts( const QList > updatedUrls ) { QList< QPair >::const_iterator tempUpdatedUrl = updatedUrls.begin(); for(; tempUpdatedUrl != updatedUrls.end(); ++tempUpdatedUrl ) { foreach( PodcastChannelPtr tempChannel, The::playlistManager()->defaultPodcasts()->channels() ) { if( tempChannel->url() == (*tempUpdatedUrl).first ) tempChannel->setUrl( (*tempUpdatedUrl).second ); } foreach( PodcastChannelPtr tempGpodderChannel, m_channels ) { if( tempGpodderChannel->url() == (*tempUpdatedUrl).first ) tempGpodderChannel->setUrl( (*tempUpdatedUrl).second ); } } } void GpodderProvider::createPlayStatusBookmark() { Meta::TrackPtr track = The::engineController()->currentTrack(); if( track ) { EpisodeActionPtr tempEpisodeAction = m_episodeStatusMap.value( QUrl( track->uidUrl() ) ); //Create an AutoTimecode at the last position position, so the user always know where he stopped to listen if( tempEpisodeAction && ( tempEpisodeAction->action() == EpisodeAction::Play ) ) { if( track && track->has() ) { QScopedPointer tcw( track->create() ); qint64 positionMiliSeconds = tempEpisodeAction->position() * 1000; tcw->writeAutoTimecode( positionMiliSeconds ); } } } } void GpodderProvider::requestUrlResolve( Podcasts::GpodderPodcastChannelPtr channel ) { if( !channel ) return; m_resolveUrlJob = KIO::get( channel->url(), KIO::Reload, KIO::HideProgressInfo ); connect( m_resolveUrlJob, &KJob::result, this, &GpodderProvider::urlResolveFinished ); connect( m_resolveUrlJob, &KIO::TransferJob::permanentRedirection, this, &GpodderProvider::urlResolvePermanentRedirection ); m_resolvedPodcasts.insert( m_resolveUrlJob, channel ); } void GpodderProvider::urlResolvePermanentRedirection( KIO::Job *job, const QUrl &fromUrl, const QUrl &toUrl ) { DEBUG_BLOCK KIO::TransferJob *transferJob = dynamic_cast( job ); GpodderPodcastChannelPtr channel = m_resolvedPodcasts.value( transferJob ); m_redirectionUrlMap.insert( toUrl, channel->url() ); channel->setUrl( toUrl ); debug() << fromUrl.url() << " was redirected to " << toUrl.url(); requestUrlResolve( channel ); } void GpodderProvider::urlResolveFinished( KJob * job ) { KIO::TransferJob *transferJob = dynamic_cast( job ); if( transferJob && ( !( transferJob->isErrorPage() || job->error() ) ) ) { m_resolvedChannelsToBeAdded.push_back( m_resolvedPodcasts.value( transferJob ) ); m_resolvedPodcasts.remove( transferJob ); } else requestUrlResolve( m_resolvedPodcasts.value( transferJob ) ); if( m_resolvedPodcasts.empty() ) continueDeviceUpdatesFinished(); m_resolveUrlJob = 0; } void GpodderProvider::slotEpisodeDownloaded( PodcastEpisodePtr episode ) { EpisodeActionPtr tempEpisodeAction; QString podcastUrl = resolvedPodcastUrl( episode ).url(); tempEpisodeAction = EpisodeActionPtr( new EpisodeAction( QUrl( podcastUrl ), QUrl( episode->uidUrl() ), m_deviceName, EpisodeAction::Download, QDateTime::currentMSecsSinceEpoch(), 0, 0, 0 ) ); //Any previous episodeAction, from the same podcast, will be replaced m_uploadEpisodeStatusMap.insert( QUrl( episode->uidUrl() ), tempEpisodeAction ); m_timerSynchronizeStatus->start( 60000 ); } void GpodderProvider::slotEpisodeDeleted( PodcastEpisodePtr episode ) { EpisodeActionPtr tempEpisodeAction; QString podcastUrl = resolvedPodcastUrl( episode ).url(); tempEpisodeAction = EpisodeActionPtr( new EpisodeAction( QUrl( podcastUrl ), QUrl( episode->uidUrl() ), m_deviceName, EpisodeAction::Delete, QDateTime::currentMSecsSinceEpoch(), 0, 0, 0 ) ); //Any previous episodeAction, from the same podcast, will be replaced m_uploadEpisodeStatusMap.insert( QUrl( episode->uidUrl() ), tempEpisodeAction ); m_timerSynchronizeStatus->start( 60000 ); } void GpodderProvider::slotEpisodeMarkedAsNew( PodcastEpisodePtr episode ) { EpisodeActionPtr tempEpisodeAction; QString podcastUrl = resolvedPodcastUrl( episode ).url(); tempEpisodeAction = EpisodeActionPtr( new EpisodeAction( QUrl( podcastUrl ), QUrl( episode->uidUrl() ), m_deviceName, EpisodeAction::New, QDateTime::currentMSecsSinceEpoch(), 0, 0, 0 ) ); //Any previous episodeAction, from the same podcast, will be replaced m_uploadEpisodeStatusMap.insert( QUrl( episode->uidUrl() ), tempEpisodeAction ); m_timerSynchronizeStatus->start( 60000 ); } inline KConfigGroup GpodderProvider::gpodderActionsConfig() const { return Amarok::config( "GPodder Cached Episode Actions" ); } void GpodderProvider::loadCachedEpisodeActions() { DEBUG_BLOCK if( !gpodderActionsConfig().exists() ) return; int action; bool validActionType; bool actionTypeConversion; qulonglong timestamp = 0; qulonglong started = 0; qulonglong position = 0; qulonglong total = 0; QStringList actionsDetails; EpisodeAction::ActionType actionType; foreach( QString episodeUrl, gpodderActionsConfig().keyList() ) { actionsDetails.clear(); actionsDetails = gpodderActionsConfig().readEntry( episodeUrl ).split( ',' ); if( actionsDetails.count() != 6 ) debug() << "There are less/more fields than expected."; else { action = actionsDetails[1].toInt( &actionTypeConversion ); if( !actionTypeConversion ) debug() << "Failed to convert actionType field to int."; else { validActionType = true; timestamp = actionsDetails[2].toULongLong(); started = actionsDetails[3].toULongLong(); position = actionsDetails[4].toULongLong(); total = actionsDetails[5].toULongLong(); switch( action ) { case 0: actionType = EpisodeAction::Download; break; case 1: actionType = EpisodeAction::Play; break; case 2: actionType = EpisodeAction::Delete; break; case 3: actionType = EpisodeAction::New; break; default: validActionType = false; break; } //We can't create a EpisodeAction if action isn't a valid alternative if( !validActionType ) debug() << "Action isn't a valid alternative."; else { debug() << QString( "Loaded %1 action." ).arg( episodeUrl ); EpisodeActionPtr tempEpisodeAction = EpisodeActionPtr( new EpisodeAction( QUrl( actionsDetails[0] ), QUrl( episodeUrl ), m_deviceName, actionType, timestamp, started, position, total ) ); //Any previous episodeAction, from the same podcast, will be replaced m_uploadEpisodeStatusMap.insert( tempEpisodeAction->episodeUrl(), tempEpisodeAction ); m_episodeStatusMap.insert( tempEpisodeAction->episodeUrl(), tempEpisodeAction ); } } } } //We should delete cached EpisodeActions, since we already loaded them gpodderActionsConfig().deleteGroup(); synchronizeStatus(); } void GpodderProvider::saveCachedEpisodeActions() { DEBUG_BLOCK if( m_uploadEpisodeStatusMap.isEmpty() ) return; int actionType; QList actionsDetails; foreach( EpisodeActionPtr action, m_uploadEpisodeStatusMap.values() ) { actionsDetails.clear(); actionsDetails.append( action->podcastUrl().toString() ); switch( action->action() ) { case EpisodeAction::Download: actionType = 0; break; case EpisodeAction::Play: actionType = 1; break; case EpisodeAction::Delete: actionType = 2; break; case EpisodeAction::New: actionType = 3; break; default: actionType = -1; break; } actionsDetails.append( QString::number( actionType ) ); actionsDetails.append( QString::number( action->timestamp() ) ); actionsDetails.append( QString::number( action->started() ) ); actionsDetails.append( QString::number( action->position() ) ); actionsDetails.append( QString::number( action->total() ) ); gpodderActionsConfig().writeEntry( action->episodeUrl().toString(), actionsDetails ); } } inline KConfigGroup GpodderProvider::gpodderPodcastsConfig() const { return Amarok::config( "GPodder Cached Podcast Changes" ); } void GpodderProvider::loadCachedPodcastsChanges() { DEBUG_BLOCK if( !gpodderPodcastsConfig().exists() ) return; QStringList podcastsUrlsToAdd; QStringList podcastsUrlsToRemove; podcastsUrlsToAdd = gpodderPodcastsConfig().readEntry( "addList" ).split( ',' ); podcastsUrlsToRemove = gpodderPodcastsConfig().readEntry( "removeList" ).split( ',' ); foreach( QString podcastUrl, podcastsUrlsToAdd ) { debug() << QString( "New channel to subscribe: %1" ).arg( podcastUrl ); m_addList.append( QUrl( podcastUrl ) ); } foreach( QString podcastUrl, podcastsUrlsToRemove ) { debug() << QString( "New channel to unsubscribe: %1 action." ).arg( podcastUrl ); m_removeList.append( QUrl( podcastUrl ) ); } //We should delete cached podcasts changes, since we already loaded them gpodderPodcastsConfig().deleteGroup(); synchronizeSubscriptions(); } void GpodderProvider::saveCachedPodcastsChanges() { DEBUG_BLOCK if( !m_addList.isEmpty() ) { QStringList podcastUrlsToAdd; foreach( QUrl podcastUrl, m_addList ) podcastUrlsToAdd.append( podcastUrl.toString() ); gpodderPodcastsConfig().writeEntry( "addList", podcastUrlsToAdd ); } if( !m_removeList.isEmpty() ) { QStringList podcastsUrlsToRemove; foreach( QUrl podcastUrl, m_removeList ) podcastsUrlsToRemove.append( podcastUrl.toString() ); gpodderPodcastsConfig().writeEntry( "removeList", podcastsUrlsToRemove ); } } QUrl GpodderProvider::resolvedPodcastUrl( const PodcastEpisodePtr episode ) { QUrl podcastUrl = episode->channel()->url(); if( m_redirectionUrlMap.contains( podcastUrl ) ) podcastUrl = m_redirectionUrlMap.value( podcastUrl ); return podcastUrl; } diff --git a/src/services/gpodder/GpodderService.cpp b/src/services/gpodder/GpodderService.cpp index 359fa445b5..67cfcc5bbb 100644 --- a/src/services/gpodder/GpodderService.cpp +++ b/src/services/gpodder/GpodderService.cpp @@ -1,274 +1,274 @@ /**************************************************************************************** * Copyright (c) 2010 - 2011 Stefan Derkits * * Copyright (c) 2010 - 2011 Christian Wagner * * Copyright (c) 2010 - 2011 Felix Winter * * * * 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 . * ****************************************************************************************/ #define DEBUG_PREFIX "GpodderService" #include "GpodderService.h" #include "core/podcasts/PodcastProvider.h" #include "core/support/Debug.h" #include "GpodderPodcastTreeItem.h" #include "GpodderServiceConfig.h" #include "GpodderServiceModel.h" #include "GpodderServiceView.h" #include "GpodderSortFilterProxyModel.h" #include #include #include "playlistmanager/PlaylistManager.h" #include "widgets/SearchWidget.h" #include #include #include GpodderServiceFactory::GpodderServiceFactory() : ServiceFactory() {} GpodderServiceFactory::~GpodderServiceFactory() {} void GpodderServiceFactory::init() { ServiceBase *service = createGpodderService(); if( service ) { m_initialized = true; emit newService( service ); } } QString GpodderServiceFactory::name() { - return "gpodder.net"; + return QStringLiteral("gpodder.net"); } KConfigGroup GpodderServiceFactory::config() { return Amarok::config( GpodderServiceConfig::configSectionName() ); } void GpodderServiceFactory::slotCreateGpodderService() { //Until we can remove a service when networking gets disabled, only create it the first time. if( !m_initialized ) { ServiceBase *service = createGpodderService(); if( service ) { m_initialized = true; emit newService( service ); } } } void GpodderServiceFactory::slotRemoveGpodderService() { if( activeServices().isEmpty() ) return; m_initialized = false; emit removeService( activeServices().first() ); } ServiceBase * GpodderServiceFactory::createGpodderService() { ServiceBase *service = new GpodderService( this, QLatin1String( "gpodder" ) ); return service; } GpodderService::GpodderService( GpodderServiceFactory *parent, const QString &name ) : ServiceBase( name, parent, false ) , m_inited( false ) , m_apiRequest( 0 ) , m_podcastProvider( 0 ) , m_proxyModel( 0 ) , m_subscribeButton( 0 ) , m_selectionModel( 0 ) { DEBUG_BLOCK setShortDescription( i18n( "gpodder.net: Podcast Directory Service" ) ); setIcon( QIcon::fromTheme( "view-services-gpodder-amarok" ) ); setLongDescription( i18n( "gpodder.net is an online Podcast Directory & Synchonisation Service." ) ); setImagePath( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/mygpo.png" ) ); init(); } GpodderService::~GpodderService() { DEBUG_BLOCK if( m_podcastProvider ) { //Remove the provider The::playlistManager()->removeProvider( m_podcastProvider ); delete m_podcastProvider; } if ( m_apiRequest ) delete m_apiRequest; } //This Method should only contain the most necessary things for initilazing the Service void GpodderService::init() { DEBUG_BLOCK GpodderServiceConfig config; const QString &username = config.username(); const QString &password = config.password(); if ( m_apiRequest ) delete m_apiRequest; //We have to check this here too, since KWallet::openWallet() doesn't //guarantee that it will always return a wallet. //Notice that LastFm service does the same verification. if ( !config.isDataLoaded() ) { debug() << "Failed to read gpodder credentials."; m_apiRequest = new mygpo::ApiRequest( The::networkAccessManager() ); } else { if( config.enableProvider() ) { m_apiRequest = new mygpo::ApiRequest( username, password, The::networkAccessManager() ); if( m_podcastProvider ) delete m_podcastProvider; enableGpodderProvider( username ); } else m_apiRequest = new mygpo::ApiRequest( The::networkAccessManager() ); } setServiceReady( true ); m_inited = true; } //This Method should contain the rest of the Service Initialization (not soo necessary things, that //can be done after the Object was created) void GpodderService::polish() { DEBUG_BLOCK generateWidgetInfo(); if( m_polished ) return; //do not allow this content to get added to the playlist. At least not for now setPlayableTracks( false ); GpodderServiceView *view = new GpodderServiceView( this ); view->setHeaderHidden( true ); view->setFrameShape( QFrame::NoFrame ); // Was set true in OpmlDirectoryService, but I think we won't need this on true view->setDragEnabled( false ); view->setItemsExpandable( true ); view->setSortingEnabled( false ); view->setEditTriggers( QAbstractItemView::NoEditTriggers ); view->setDragDropMode( QAbstractItemView::NoDragDrop ); setView( view ); GpodderServiceModel *sourceModel = new GpodderServiceModel( m_apiRequest, this ); m_proxyModel = new GpodderSortFilterProxyModel( this ); m_proxyModel->setDynamicSortFilter( true ); m_proxyModel->setFilterCaseSensitivity( Qt::CaseInsensitive ); m_proxyModel->setSourceModel( sourceModel ); setModel( m_proxyModel ); m_selectionModel = view->selectionModel(); m_subscribeButton = new QPushButton(); m_subscribeButton->setParent( m_bottomPanel ); m_subscribeButton->setText( i18n( "Subscribe" ) ); m_subscribeButton->setObjectName( "subscribeButton" ); m_subscribeButton->setIcon( QIcon::fromTheme( "get-hot-new-stuff-amarok" ) ); m_subscribeButton->setEnabled( true ); connect( m_subscribeButton, &QPushButton::clicked, this, &GpodderService::subscribe ); connect( m_searchWidget, &SearchWidget::filterChanged, m_proxyModel, &QSortFilterProxyModel::setFilterWildcard ); m_polished = true; } void GpodderService::itemSelected( CollectionTreeItem * selectedItem ) { Q_UNUSED( selectedItem ) DEBUG_BLOCK return; } void GpodderService::subscribe() { QModelIndex index = m_proxyModel->mapToSource( m_selectionModel->currentIndex() ); GpodderTreeItem *treeItem = static_cast( index.internalPointer() ); if( GpodderPodcastTreeItem *podcastTreeItem = qobject_cast( treeItem ) ) { Podcasts::PodcastProvider *podcastProvider = The::playlistManager()->defaultPodcasts(); QUrl kUrl( podcastTreeItem->podcast()->url() ); podcastProvider->addPodcast( kUrl ); } } void GpodderService::enableGpodderProvider( const QString &username ) { DEBUG_BLOCK QString deviceName = QLatin1String( "amarok-" ) % QHostInfo::localHostName(); debug() << QString( "Enabling GpodderProvider( Username: %1 - Device: %1 )" ) .arg( username ) .arg( deviceName ); m_podcastProvider = new Podcasts::GpodderProvider( username, deviceName, m_apiRequest ); //Add the gpodder's provider to the playlist manager The::playlistManager()->addProvider( m_podcastProvider, PlaylistManager::PodcastChannel ); } diff --git a/src/services/gpodder/GpodderServiceConfig.cpp b/src/services/gpodder/GpodderServiceConfig.cpp index b980ae168c..e9147f16bf 100644 --- a/src/services/gpodder/GpodderServiceConfig.cpp +++ b/src/services/gpodder/GpodderServiceConfig.cpp @@ -1,239 +1,237 @@ /**************************************************************************************** * Copyright (c) 2007 Shane King * * Copyright (c) 2009 Leo Franchi * * Copyright (c) 2010 Stefan Derkits * * Copyright (c) 2010 Christian Wagner * * Copyright (c) 2010 Felix Winter * * * * 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 . * ****************************************************************************************/ #define DEBUG_PREFIX "GPodderConfig" #include "GpodderServiceConfig.h" #include "App.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include #include #include #include #include GpodderServiceConfig::GpodderServiceConfig() - : m_username( "" ) - , m_password( "" ) - , m_enableProvider( false ) + : m_enableProvider( false ) , m_ignoreWallet( false ) , m_isDataLoaded( false ) - , m_askDiag( 0 ) - , m_wallet( 0 ) + , m_askDiag( nullptr ) + , m_wallet( nullptr ) { DEBUG_BLOCK load(); } GpodderServiceConfig::~GpodderServiceConfig() { DEBUG_BLOCK if( m_askDiag ) m_askDiag->deleteLater(); if( m_wallet ) m_wallet->deleteLater(); } void GpodderServiceConfig::load() { DEBUG_BLOCK debug() << "Load config"; KConfigGroup config = Amarok::config( configSectionName() ); m_enableProvider = config.readEntry( "enableProvider", false ); m_ignoreWallet = config.readEntry( "ignoreWallet", false ); //We only want to load the wallet if the user has enabled features that require a user/pass tryToOpenWallet(); if( m_wallet ) { if( !m_wallet->hasFolder( "Amarok" ) ) m_wallet->createFolder( "Amarok" ); // do a one-time transfer // can remove at some point in the future, post-2.2 m_wallet->setFolder( "Amarok" ); if( m_wallet->readPassword( "gpodder_password", m_password ) != 0 ) debug() << "Failed to read gpodder.net password from kwallet!"; else { QByteArray rawUsername; if( m_wallet->readEntry( "gpodder_username", rawUsername ) != 0 ) debug() << "Failed to read gpodder.net username from kwallet.. :("; else m_username = QString::fromUtf8( rawUsername ); } } else if( m_ignoreWallet ) { m_username = config.readEntry( "username", QString() ); m_password = config.readEntry( "password", QString() ); } else debug() << "Failed to load the data."; m_isDataLoaded = !( m_username.isEmpty() || m_password.isEmpty() ); } void GpodderServiceConfig::save() { DEBUG_BLOCK debug() << "Save config"; KConfigGroup config = Amarok::config( configSectionName() ); config.writeEntry( "enableProvider", m_enableProvider ); config.writeEntry( "ignoreWallet", m_ignoreWallet ); //Whenever this function is called, we'll assume the user wants to //change something, so blow away the subscription timestamp key config.writeEntry( "subscriptionTimestamp", 0 ); //Maybe the wallet had already closed or m_enableProvider and m_ignoreWallet //could had changed also. So we try to reopen the wallet if it's not open. tryToOpenWallet(); if( m_wallet ) { m_wallet->setFolder( "Amarok" ); if( m_wallet->writeEntry( "gpodder_username", m_username.toUtf8() ) != 0 ) debug() << "Failed to save gpodder.net username to kwallet!"; if( m_wallet->writePassword( "gpodder_password", m_password ) != 0 ) debug() << "Failed to save gpodder.net pw to kwallet!"; } else { if( m_enableProvider ) { debug() << "Couldn't access the wallet to save the gpodder.net credentials"; askAboutMissingKWallet(); } else debug() << "There isn't valid credentials to be saved"; } config.sync(); } void GpodderServiceConfig::askAboutMissingKWallet() { if ( !m_askDiag ) { - m_askDiag = new QMessageBox( Q_NULLPTR ); + m_askDiag = new QMessageBox( nullptr ); m_askDiag->setWindowTitle( i18n( "gpodder.net credentials" ) ); m_askDiag->setText( i18n( "No running KWallet found. Would you like Amarok to save your gpodder.net credentials in plaintext?" ) ); m_askDiag->setStandardButtons( QMessageBox::Yes | QMessageBox::No ); m_askDiag->setModal( true ); connect( m_askDiag, &QMessageBox::accepted, this, &GpodderServiceConfig::textDialogYes ); connect( m_askDiag, &QMessageBox::rejected, this, &GpodderServiceConfig::textDialogNo ); } m_askDiag->exec(); } void GpodderServiceConfig::tryToOpenWallet() { DEBUG_BLOCK //We only want to load the wallet if the user has enabled features //that require a user/pass if( ( m_enableProvider ) && ( !m_ignoreWallet ) ) { debug() << "Opening wallet"; //Open wallet unless explicitly told not to m_wallet = KWallet::Wallet::openWallet( KWallet::Wallet::NetworkWallet(), 0 ); } else { debug() << "The wallet was ignored or is not needed."; - m_wallet = 0; + m_wallet = nullptr; } } void GpodderServiceConfig::reset() { debug() << "Reset config"; m_username = ""; m_password = ""; m_enableProvider = false; m_ignoreWallet = false; } void GpodderServiceConfig::textDialogYes() //SLOT { DEBUG_BLOCK if ( !m_ignoreWallet ) { KConfigGroup config = Amarok::config( configSectionName() ); m_ignoreWallet = true; config.writeEntry( "ignoreWallet", m_ignoreWallet ); config.writeEntry( "username", m_username ); config.writeEntry( "password", m_password ); config.sync(); } } void GpodderServiceConfig::textDialogNo() //SLOT { DEBUG_BLOCK if ( m_ignoreWallet ) { KConfigGroup config = Amarok::config( configSectionName() ); m_ignoreWallet = false; config.writeEntry( "ignoreWallet", m_ignoreWallet ); config.writeEntry( "username", QString() ); config.writeEntry( "password", QString() ); config.sync(); } } diff --git a/src/services/gpodder/GpodderServiceModel.cpp b/src/services/gpodder/GpodderServiceModel.cpp index b7db19cf92..8fd35f0622 100644 --- a/src/services/gpodder/GpodderServiceModel.cpp +++ b/src/services/gpodder/GpodderServiceModel.cpp @@ -1,396 +1,396 @@ /**************************************************************************************** * Copyright (c) 2011 Stefan Derkits * * Copyright (c) 2011 Christian Wagner * * Copyright (c) 2011 Felix Winter * * * * 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 "GpodderServiceModel.h" #include "core/support/Debug.h" #include "GpodderPodcastRequestHandler.h" #include "GpodderPodcastTreeItem.h" #include "GpodderServiceSettings.h" #include "GpodderTagTreeItem.h" #include #include #include #include static const int s_numberItemsToLoad = 100; using namespace mygpo; GpodderServiceModel::GpodderServiceModel( ApiRequest *request, QObject *parent ) : QAbstractItemModel( parent ) - , m_rootItem( 0 ) - , m_topTagsItem( 0 ) - , m_topPodcastsItem( 0 ) - , m_suggestedPodcastsItem( 0 ) - , m_topTags( 0 ) + , m_rootItem( nullptr ) + , m_topTagsItem( nullptr ) + , m_topPodcastsItem( nullptr ) + , m_suggestedPodcastsItem( nullptr ) + , m_topTags( nullptr ) , m_apiRequest( request ) { GpodderServiceConfig config; m_rootItem = new GpodderTreeItem( ); m_topTagsItem = new GpodderTreeItem( m_rootItem, "Top Tags" ); m_rootItem->appendChild( m_topTagsItem ); m_topPodcastsItem = new GpodderTreeItem( m_rootItem, "Top Podcasts" ); m_rootItem->appendChild( m_topPodcastsItem ); if ( config.isDataLoaded() && config.enableProvider() ) { m_suggestedPodcastsItem = new GpodderTreeItem( m_rootItem, "Suggested Podcasts" ); m_rootItem->appendChild( m_suggestedPodcastsItem ); } } GpodderServiceModel::~GpodderServiceModel() { delete m_rootItem; } QModelIndex GpodderServiceModel::index( int row, int column, const QModelIndex &parent ) const { if( !hasIndex( row, column, parent ) ) return QModelIndex(); GpodderTreeItem *parentItem; if( !parent.isValid() ) parentItem = m_rootItem; else parentItem = static_cast( parent.internalPointer() ); - if( parentItem == 0 ) + if( parentItem == nullptr ) return QModelIndex(); GpodderTreeItem *childItem = parentItem->child( row ); if( childItem ) return createIndex( row, column, childItem ); else return QModelIndex(); } QModelIndex GpodderServiceModel::parent( const QModelIndex &index ) const { if( !index.isValid() ) return QModelIndex(); GpodderTreeItem *childItem = static_cast( index.internalPointer() ); - if( childItem == 0 || childItem->isRoot() ) + if( childItem == nullptr || childItem->isRoot() ) return QModelIndex(); GpodderTreeItem *parentItem = childItem->parent(); - if( parentItem == 0 ) + if( parentItem == nullptr ) return QModelIndex(); int childIndex; if( parentItem->isRoot() ) return QModelIndex(); else childIndex = parentItem->parent()->children().indexOf( parentItem ); return createIndex( childIndex, 0, parentItem ); } int GpodderServiceModel::rowCount( const QModelIndex &parent ) const { GpodderTreeItem *parentItem; if( !parent.isValid() ) { return m_rootItem->childCount(); } parentItem = static_cast( parent.internalPointer() ); - if( parentItem == 0 ) + if( parentItem == nullptr ) return 0; return parentItem->childCount(); } int GpodderServiceModel::columnCount( const QModelIndex &parent ) const { Q_UNUSED( parent ) return 1; } QVariant GpodderServiceModel::data( const QModelIndex &index, int role ) const { if( !index.isValid() ) return QVariant(); if( role != Qt::DisplayRole ) return QVariant(); GpodderTreeItem *item = static_cast( index.internalPointer() ); - if( item == 0 ) + if( item == nullptr ) { return QVariant(); } return item->displayData(); } void GpodderServiceModel::insertTagList() { - if( m_rootItem != 0 ) + if( m_rootItem != nullptr ) { beginInsertRows( createIndex( 0,0, m_topTagsItem), 0, m_topTags->list().count() - 1 ); m_topTagsItem->appendTags( m_topTags ); endInsertRows(); } } void GpodderServiceModel::topTagsRequestError( QNetworkReply::NetworkError error ) { DEBUG_BLOCK debug() << "Error in TopTags request: " << error; QTimer::singleShot( 20000, this, &GpodderServiceModel::requestTopTags ); } void GpodderServiceModel::topTagsParseError() { DEBUG_BLOCK debug() << "Error while parsing TopTags"; QTimer::singleShot( 20000, this, &GpodderServiceModel::requestTopTags ); } void GpodderServiceModel::topPodcastsRequestError( QNetworkReply::NetworkError error ) { DEBUG_BLOCK debug() << "Error in TopPodcasts request: " << error; QTimer::singleShot( 20000, this, &GpodderServiceModel::requestTopPodcasts ); } void GpodderServiceModel::topPodcastsParseError() { DEBUG_BLOCK debug() << "Error while parsing TopPodcasts"; QTimer::singleShot( 20000, this, &GpodderServiceModel::requestTopPodcasts ); } void GpodderServiceModel::suggestedPodcastsRequestError( QNetworkReply::NetworkError error ) { DEBUG_BLOCK debug() << "Error in suggestedPodcasts request: " << error; QTimer::singleShot( 20000, this, &GpodderServiceModel::requestSuggestedPodcasts ); } void GpodderServiceModel::suggestedPodcastsParseError() { DEBUG_BLOCK debug() << "Error while parsing suggestedPodcasts"; QTimer::singleShot( 20000, this, &GpodderServiceModel::requestSuggestedPodcasts ); } void GpodderServiceModel::insertPodcastList( mygpo::PodcastListPtr podcasts, const QModelIndex &parentItem ) { DEBUG_BLOCK emit layoutAboutToBeChanged(); beginInsertRows( parentItem, 0, podcasts->list().count() - 1 ); GpodderTreeItem *item = static_cast( parentItem.internalPointer() ); - if( item != 0 ) + if( item != nullptr ) { debug() << "Appending Podcasts..."; item->appendPodcasts( podcasts ); } endInsertRows(); emit layoutChanged(); } bool GpodderServiceModel::hasChildren( const QModelIndex &parent ) const { if( !parent.isValid() ) return true; GpodderTreeItem *treeItem = static_cast( parent.internalPointer() ); - if( treeItem == 0 ) + if( treeItem == nullptr ) return false; if( treeItem->childCount() > 0 ) return true; if( !qobject_cast( treeItem ) ) { return true; } else { return false; } } bool GpodderServiceModel::canFetchMore( const QModelIndex &parent ) const { // root item if( !parent.isValid() ) { return !m_rootItem->hasChildren(); } // already fetched or just started? GpodderTreeItem *treeItem = static_cast( parent.internalPointer() ); if( treeItem == 0 || treeItem->hasChildren() /* || m_currentFetchingMap.values().contains( parent ) */ ) { return false; } // TagTreeItem if( qobject_cast( treeItem ) ) { if( !QNetworkConfigurationManager().isOnline() ) return false; return true; } return false; } void GpodderServiceModel::fetchMore( const QModelIndex &parent ) { // root item if( !parent.isValid() ) { requestTopTags(); requestTopPodcasts(); - if ( m_suggestedPodcastsItem != 0 ) + if ( m_suggestedPodcastsItem != nullptr ) requestSuggestedPodcasts(); } GpodderTreeItem *treeItem = static_cast( parent.internalPointer() ); // TagTreeItem if( GpodderTagTreeItem *tagTreeItem = qobject_cast( treeItem ) ) { m_rootItem->setHasChildren( true ); tagTreeItem->setHasChildren( true ); mygpo::PodcastListPtr podcasts = m_apiRequest->podcastsOfTag( s_numberItemsToLoad, tagTreeItem->tag()->tag() ); GpodderPodcastRequestHandler *podcastRequestHandler = new GpodderPodcastRequestHandler( podcasts, parent, this ); connect( podcasts.data(), SIGNAL(finished()), podcastRequestHandler, SLOT(finished()) ); connect( podcasts.data(), SIGNAL(requestError(QNetworkReply::NetworkError)), podcastRequestHandler, SLOT(requestError(QNetworkReply::NetworkError)) ); connect( podcasts.data(), SIGNAL(parseError()), podcastRequestHandler, SLOT(parseError()) ); } } void GpodderServiceModel::requestTopTags() { if( !QNetworkConfigurationManager().isOnline() ) { QTimer::singleShot( 10000, this, SLOT(requestTopTags()) ); return; } m_rootItem->setHasChildren( true ); m_topTags = m_apiRequest->topTags( s_numberItemsToLoad ); connect( m_topTags.data(), SIGNAL(finished()), this, SLOT(insertTagList()) ); connect( m_topTags.data(), SIGNAL(requestError(QNetworkReply::NetworkError)), SLOT(topTagsRequestError(QNetworkReply::NetworkError)) ); connect( m_topTags.data(), SIGNAL(parseError()), SLOT(topTagsParseError()) ); } void GpodderServiceModel::requestTopPodcasts() { if( !QNetworkConfigurationManager().isOnline() ) { QTimer::singleShot( 10000, this, SLOT(requestTopPodcasts()) ); return; } m_rootItem->setHasChildren( true ); mygpo::PodcastListPtr topPodcasts = m_apiRequest->toplist( s_numberItemsToLoad ); GpodderPodcastRequestHandler *podcastRequestHandler = new GpodderPodcastRequestHandler( topPodcasts, createIndex( 0,0, m_topPodcastsItem ), this ); connect( topPodcasts.data(), SIGNAL(finished()), podcastRequestHandler, SLOT(finished()) ); connect( topPodcasts.data(), SIGNAL(requestError(QNetworkReply::NetworkError)), SLOT(topPodcastsRequestError(QNetworkReply::NetworkError)) ); connect( topPodcasts.data(), SIGNAL(parseError()), SLOT(topPodcastsParseError()) ); } void GpodderServiceModel::requestSuggestedPodcasts() { if( !QNetworkConfigurationManager().isOnline() ) { QTimer::singleShot( 10000, this, SLOT(requestSuggestedPodcasts()) ); return; } m_rootItem->setHasChildren( true ); mygpo::PodcastListPtr topSuggestions = m_apiRequest->suggestions( s_numberItemsToLoad ); GpodderPodcastRequestHandler *podcastRequestHandler = new GpodderPodcastRequestHandler( topSuggestions, createIndex( 0,0, m_suggestedPodcastsItem ), this ); connect( topSuggestions.data(), SIGNAL(finished()), podcastRequestHandler, SLOT(finished()) ); connect( topSuggestions.data(), SIGNAL(requestError(QNetworkReply::NetworkError)), SLOT(suggestedPodcastsRequestError(QNetworkReply::NetworkError)) ); connect( topSuggestions.data(), SIGNAL(parseError()), SLOT(suggestedPodcastsParseError()) ); } diff --git a/src/services/gpodder/GpodderServiceSettings.cpp b/src/services/gpodder/GpodderServiceSettings.cpp index 7ff5a4c60d..e8d7c3ebe8 100644 --- a/src/services/gpodder/GpodderServiceSettings.cpp +++ b/src/services/gpodder/GpodderServiceSettings.cpp @@ -1,256 +1,256 @@ /**************************************************************************************** * Copyright (c) 2007 Shane King * * Copyright (c) 2010 Stefan Derkits * * Copyright (c) 2010 Christian Wagner * * Copyright (c) 2010 Felix Winter * * * * 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 . * ****************************************************************************************/ #define DEBUG_PREFIX "GpodderServiceSettings" #include "GpodderServiceSettings.h" #include "core/podcasts/PodcastProvider.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "NetworkAccessManagerProxy.h" #include "playlistmanager/PlaylistManager.h" #include "ui_GpodderConfigWidget.h" #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON( GpodderServiceSettingsFactory, "amarok_service_gpodder_config.json", registerPlugin(); ) GpodderServiceSettings::GpodderServiceSettings( QWidget *parent, const QVariantList &args ) : KCModule( parent, args ) , m_enableProvider( false ) - , m_createDevice( 0 ) + , m_createDevice( nullptr ) { debug() << "Creating gpodder.net config object"; m_configDialog = new Ui::GpodderConfigWidget; m_configDialog->setupUi( this ); connect( m_configDialog->kcfg_GpodderUsername, &QLineEdit::textChanged, this, &GpodderServiceSettings::settingsChanged ); connect( m_configDialog->kcfg_GpodderPassword, &QLineEdit::textChanged, this, &GpodderServiceSettings::settingsChanged ); connect( m_configDialog->testLogin, &QPushButton::clicked, this, &GpodderServiceSettings::testLogin ); load(); } GpodderServiceSettings::~GpodderServiceSettings() { if( m_createDevice ) m_createDevice->deleteLater(); if( m_devices ) m_devices->deleteLater(); delete m_configDialog; } void GpodderServiceSettings::save() { m_config.setUsername( m_configDialog->kcfg_GpodderUsername->text() ); m_config.setPassword( m_configDialog->kcfg_GpodderPassword->text() ); m_config.setEnableProvider( m_enableProvider ); m_config.setIgnoreWallet( false ); m_config.save(); KCModule::save(); } void GpodderServiceSettings::testLogin() { DEBUG_BLOCK if ( ( !m_configDialog->kcfg_GpodderUsername->text().isEmpty() ) && ( !m_configDialog->kcfg_GpodderPassword->text().isEmpty() ) ) { m_configDialog->testLogin->setEnabled( false ); m_configDialog->testLogin->setText( i18n( "Testing..." ) ); mygpo::ApiRequest api( m_configDialog->kcfg_GpodderUsername->text(), m_configDialog->kcfg_GpodderPassword->text(), The::networkAccessManager() ); m_devices = api.listDevices( m_configDialog->kcfg_GpodderUsername->text() ); connect( m_devices.data(), &mygpo::DeviceList::finished, this, &GpodderServiceSettings::finished ); connect( m_devices.data(), &mygpo::DeviceList::requestError, this, &GpodderServiceSettings::onError ); connect( m_devices.data(), &mygpo::DeviceList::parseError, this, &GpodderServiceSettings::onParseError ); } else { KMessageBox::error( this, i18n( "Either the username or the password is empty, please correct and try again." ), i18n( "Failed" ) ); } } void GpodderServiceSettings::finished() { DEBUG_BLOCK debug() << "Authentication worked, got List of Devices, searching for Amarok Device"; m_configDialog->testLogin->setText( i18nc( "The operation completed as expected", "Success" ) ); m_configDialog->testLogin->setEnabled( false ); bool deviceExists = false; QList ptrList = m_devices->devicesList(); mygpo::DevicePtr devPtr; QString hostname = QHostInfo::localHostName(); QString deviceID = QLatin1String( "amarok-" ) % hostname; foreach( devPtr, ptrList ) { if( devPtr->id().compare( deviceID ) == 0 ) { deviceExists = true; break; } } if( !deviceExists ) { debug() << "Create new device " % deviceID; mygpo::ApiRequest api( m_configDialog->kcfg_GpodderUsername->text(), m_configDialog->kcfg_GpodderPassword->text(), The::networkAccessManager() ); m_createDevice = api.renameDevice( m_configDialog->kcfg_GpodderUsername->text(), deviceID, QLatin1String( "Amarok on " ) % hostname, mygpo::Device::OTHER ); connect( m_createDevice, &QNetworkReply::finished, this, &GpodderServiceSettings::deviceCreationFinished ); connect( m_createDevice, QOverload::of(&QNetworkReply::error), this, &GpodderServiceSettings::deviceCreationError ); } else { debug() << "Amarok device was found and everything looks perfect"; } } void GpodderServiceSettings::onError( QNetworkReply::NetworkError code ) { DEBUG_BLOCK debug() << code; if( code == QNetworkReply::NoError ) debug() << "No Error was found, but onError was called - should not happen"; else if( code == QNetworkReply::AuthenticationRequiredError ) { debug() << "Authentication failed"; KMessageBox::error( this, i18n( "Either the username or the password is incorrect, please correct and try again" ), i18n( "Failed" ) ); m_configDialog->testLogin->setText( i18n( "&Test Login" ) ); m_configDialog->testLogin->setEnabled( true ); } else { KMessageBox::error( this, i18n( "Unable to connect to gpodder.net service or other error occurred." ), i18n( "Failed" ) ); m_configDialog->testLogin->setText( i18n( "&Test Login" ) ); m_configDialog->testLogin->setEnabled( true ); } } void GpodderServiceSettings::onParseError() { debug() << "Couldn't parse DeviceList, should not happen if gpodder.net is working correctly"; m_configDialog->testLogin->setText( i18n( "&Test Login" ) ); m_configDialog->testLogin->setEnabled( true ); KMessageBox::error( this, i18n( "Error parsing the Reply, check if gpodder.net is working correctly and report a bug" ), i18n( "Failed" ) ); } void GpodderServiceSettings::deviceCreationFinished() { debug() << "Creation of Amarok Device finished"; } void GpodderServiceSettings::deviceCreationError( QNetworkReply::NetworkError code ) { debug() << "Error creating Amarok Device"; debug() << code; m_configDialog->testLogin->setText( i18n( "&Test Login" ) ); m_configDialog->testLogin->setEnabled( true ); } void GpodderServiceSettings::load() { m_config.load(); m_configDialog->kcfg_GpodderUsername->setText( m_config.username() ); m_configDialog->kcfg_GpodderPassword->setText( m_config.password() ); m_enableProvider = m_config.enableProvider(); KCModule::load(); } void GpodderServiceSettings::defaults() { m_config.reset(); - m_configDialog->kcfg_GpodderUsername->setText( "" ); - m_configDialog->kcfg_GpodderPassword->setText( "" ); + m_configDialog->kcfg_GpodderUsername->clear(); + m_configDialog->kcfg_GpodderPassword->clear(); m_enableProvider = false; } void GpodderServiceSettings::settingsChanged() { m_configDialog->testLogin->setText( i18n( "&Test Login" ) ); m_configDialog->testLogin->setEnabled( true ); m_enableProvider = true; emit changed( true ); } #include "GpodderServiceSettings.moc" diff --git a/src/services/gpodder/GpodderSortFilterProxyModel.h b/src/services/gpodder/GpodderSortFilterProxyModel.h index d5b98ca7ad..b56592e9bb 100644 --- a/src/services/gpodder/GpodderSortFilterProxyModel.h +++ b/src/services/gpodder/GpodderSortFilterProxyModel.h @@ -1,38 +1,38 @@ /**************************************************************************************** * Copyright (c) 2011 Stefan Derkits * * Copyright (c) 2011 Christian Wagner * * Copyright (c) 2011 Felix Winter * * * * 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 GPODDERSORTFILTERPROXYMODEL_H_ #define GPODDERSORTFILTERPROXYMODEL_H_ #include "GpodderServiceModel.h" #include class GpodderSortFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: explicit GpodderSortFilterProxyModel( QObject *parent = nullptr ); - virtual ~GpodderSortFilterProxyModel(); + ~GpodderSortFilterProxyModel() override; protected: - virtual bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const; + bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const override; }; #endif /* GPODDERSORTFILTERPROXYMODEL_H_ */ diff --git a/src/services/gpodder/GpodderTreeItem.cpp b/src/services/gpodder/GpodderTreeItem.cpp index e84b5d81d9..4cd3c98c84 100644 --- a/src/services/gpodder/GpodderTreeItem.cpp +++ b/src/services/gpodder/GpodderTreeItem.cpp @@ -1,103 +1,103 @@ /**************************************************************************************** * Copyright (c) 2011 Stefan Derkits * * Copyright (c) 2011 Christian Wagner * * Copyright (c) 2011 Felix Winter * * * * 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 "GpodderTreeItem.h" #include "GpodderPodcastTreeItem.h" #include "GpodderServiceModel.h" #include "GpodderTagTreeItem.h" GpodderTreeItem::GpodderTreeItem( GpodderTreeItem *parent, const QString &name ) : QObject( parent ) , m_parentItem( parent ) , m_name( name ) , m_hasChildren( false ) { } GpodderTreeItem::~GpodderTreeItem() { qDeleteAll( m_childItems ); } void GpodderTreeItem::appendChild( GpodderTreeItem *item ) { m_childItems.append( item ); } GpodderTreeItem * GpodderTreeItem::child( int row ) { return m_childItems.value( row ); } bool GpodderTreeItem::hasChildren() const { return m_hasChildren; } void GpodderTreeItem::setHasChildren( bool hasChildren ) { m_hasChildren = hasChildren; } int GpodderTreeItem::childCount() const { return m_childItems.count(); } GpodderTreeItem * GpodderTreeItem::parent() const { return m_parentItem; } QVariant GpodderTreeItem::displayData() const { return m_name; } bool GpodderTreeItem::isRoot() const { - return ( m_parentItem == 0 ); + return ( m_parentItem == nullptr ); } void GpodderTreeItem::appendTags( mygpo::TagListPtr tags ) { for( const auto &tag : tags->list() ) { GpodderTagTreeItem *treeItem = new GpodderTagTreeItem( tag, this ); appendChild( treeItem ); } } void GpodderTreeItem::appendPodcasts( mygpo::PodcastListPtr podcasts ) { for( const auto &podcast : podcasts->list() ) { appendChild( new GpodderPodcastTreeItem( podcast, this ) ); } }