[kde-doc-english] [amarok] /: GPodder.net podcast status synchronzation.

Bart Cerneels bart.cerneels at kde.org
Fri Oct 14 09:00:57 UTC 2011


Git commit 720ccbd61d93651e3b394862a55b7e3f72f07b0c by Bart Cerneels.
Committed on 14/10/2011 at 10:48.
Pushed by shanachie into branch 'master'.

GPodder.net podcast status synchronzation.

Based on playlust synchronization.

Result of the 2011 Google Summer of Code project by Lucas Lira Gomes.

Podcast subscriptions on gpodder.net will be automatically added and
synchronized with Amarok's local podcast provider.
Actions performed on the local podcasts will be send to the gpodder.net
service thus enabling synchronized podcast listening between Amarok and
other devices and computers running a gpodder.net compatible client.

CCMAIL: gpodder-devel at lists.berlios.de
REVIEW: 102844
GUI:GPodder.net podcast provider, automatically synced with local podcasts.
DIGEST:GSoC project merged into Amarok master.

M  +7    -0    ChangeLog
M  +6    -0    src/CMakeLists.txt
M  +27   -1    src/services/gpodder/CMakeLists.txt
A  +122  -0    src/services/gpodder/GpodderConfigWidget.ui
C  +37   -33   src/services/gpodder/GpodderPodcastMeta.cpp [from: src/services/gpodder/GpodderTreeItem.h - 054% similarity]
C  +32   -29   src/services/gpodder/GpodderPodcastMeta.h [from: src/services/gpodder/GpodderTreeItem.h - 056% similarity]
A  +894  -0    src/services/gpodder/GpodderProvider.cpp     [License: GPL (v2+)]
A  +163  -0    src/services/gpodder/GpodderProvider.h     [License: GPL (v2+)]
M  +60   -27   src/services/gpodder/GpodderService.cpp
M  +13   -11   src/services/gpodder/GpodderService.h
A  +195  -0    src/services/gpodder/GpodderServiceConfig.cpp     [License: GPL (v2+)]
C  +39   -51   src/services/gpodder/GpodderServiceConfig.h [from: src/services/gpodder/GpodderService.h - 053% similarity]
M  +162  -53   src/services/gpodder/GpodderServiceModel.cpp
M  +13   -1    src/services/gpodder/GpodderServiceModel.h
A  +233  -0    src/services/gpodder/GpodderServiceSettings.cpp     [License: GPL (v2+)]
C  +34   -49   src/services/gpodder/GpodderServiceSettings.h [from: src/services/gpodder/GpodderService.h - 056% similarity]
M  +26   -13   src/services/gpodder/GpodderTreeItem.cpp
M  +4    -3    src/services/gpodder/GpodderTreeItem.h
A  +12   -0    src/services/gpodder/amarok_service_gpodder_config.desktop

http://commits.kde.org/amarok/720ccbd61d93651e3b394862a55b7e3f72f07b0c

diff --git a/ChangeLog b/ChangeLog
index 049f647..573d8d1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -4,11 +4,18 @@ Amarok ChangeLog
 
 Version 2.4.4-Beta 1
   FEATURES:
+    * Podcast subscription synchronisation and podcast status synchronisation with Gpodder service.
+    * Possibility to browse through podcasts suggested by gpodder.net and through gpodder.net top
+      recommended podcasts.
     * Made possible to make presets for TagGuesser. (BR 264632)
     * Display current timestamp in tray tooltip. (BR 278445)
     * Auto-save the playlist so that it is not lost if Amarok crashes.
 
   CHANGES:
+    * Added local podcasts are automatically subscribed in gpodder.net.
+    * Removed local podcasts are automatically unsubscribed from gpodder.net.
+    * GPodder Service automatically subscribes all local podcasts into gpodder.net when the login is
+    successfull.
     * Improved the playlist synchronisation feature.
     * Make possible to get count tracks before the XSPF file is fully loaded.
     * Mark unplayble tracks in the playlist. Patch by Sandeep Raghuraman. (BR 263640)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 962070d..a822165 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -407,6 +407,7 @@ set(libplaylistmanager_SRCS
     playlistmanager/sql/SqlPlaylist.cpp
     playlistmanager/sql/SqlPlaylistGroup.cpp
     playlistmanager/SyncedPlaylist.cpp
+    playlistmanager/SyncedPodcast.cpp
     playlistmanager/SyncRelationStorage.cpp
 )
 
@@ -774,6 +775,11 @@ if(KDE4_BUILD_TESTS)
     )
 endif()
 
+if( LIBMYGPO_QT_FOUND )
+    set( EXTRA_LIBS ${LIBMYGPO_QT_LIBRARY} )
+    include_directories(     ${LIBMYGPO_QT_INCLUDE_DIR}  ${LIBMYGPO_QT_INCLUDE_DIR}/../ )
+endif( LIBMYGPO_QT_FOUND )
+
 if( LIBLASTFM_FOUND )
     set(amaroklib_LIB_SRCS
         ${amaroklib_LIB_SRCS}
diff --git a/src/services/gpodder/CMakeLists.txt b/src/services/gpodder/CMakeLists.txt
index 97f14b3..9c84d99 100644
--- a/src/services/gpodder/CMakeLists.txt
+++ b/src/services/gpodder/CMakeLists.txt
@@ -22,8 +22,11 @@ include_directories(
 
         set(amarok_service_gpodder_PART_SRCS
             GpodderService.cpp
+            GpodderServiceConfig.cpp
             GpodderServiceModel.cpp
             GpodderServiceView.cpp
+            GpodderProvider.cpp
+            GpodderPodcastMeta.cpp
             GpodderTreeItem.cpp
             GpodderPodcastTreeItem.cpp
             GpodderTagTreeItem.cpp
@@ -51,4 +54,27 @@ include_directories(
 
          install( TARGETS amarok_service_gpodder DESTINATION ${PLUGIN_INSTALL_DIR} )
 
-         install( FILES amarok_service_gpodder.desktop DESTINATION ${SERVICES_INSTALL_DIR} )
\ No newline at end of file
+         set(kcm_amarok_service_gpodder_PART_SRCS
+            GpodderServiceSettings.cpp
+            GpodderServiceConfig.cpp
+         )
+
+         set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}")
+
+         kde4_add_ui_files( kcm_amarok_service_gpodder_PART_SRCS GpodderConfigWidget.ui )
+
+         kde4_add_plugin( kcm_amarok_service_gpodder ${kcm_amarok_service_gpodder_PART_SRCS} )
+
+         target_link_libraries( kcm_amarok_service_gpodder
+            amarokcore
+            amaroklib
+            ${LIBMYGPO_QT_LIBRARY}
+            ${KDE4_KDEUI_LIBS}
+            ${KDE4_KUTILS_LIBS}
+            ${KDE4_KIO_LIBS}
+            ${QT_QTNETWORK_LIBRARY} )
+
+         install(TARGETS kcm_amarok_service_gpodder  DESTINATION ${PLUGIN_INSTALL_DIR})
+
+         install( FILES amarok_service_gpodder.desktop DESTINATION ${SERVICES_INSTALL_DIR} )
+         install( FILES amarok_service_gpodder_config.desktop DESTINATION ${SERVICES_INSTALL_DIR} )
diff --git a/src/services/gpodder/GpodderConfigWidget.ui b/src/services/gpodder/GpodderConfigWidget.ui
new file mode 100644
index 0000000..a34ae52
--- /dev/null
+++ b/src/services/gpodder/GpodderConfigWidget.ui
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>GpodderConfigWidget</class>
+ <widget class="QWidget" name="GpodderConfigWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>341</width>
+    <height>162</height>
+   </rect>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>1</width>
+    <height>1</height>
+   </size>
+  </property>
+  <layout class="QGridLayout" name="gridLayout_2">
+   <property name="margin">
+    <number>0</number>
+   </property>
+   <item row="0" column="0" rowspan="2">
+    <widget class="QGroupBox" name="groupBox3">
+     <property name="title">
+      <string>gpodder.net Profile</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout">
+      <item row="1" column="0">
+       <widget class="QLabel" name="labelUsername">
+        <property name="text">
+         <string>&Username:</string>
+        </property>
+        <property name="wordWrap">
+         <bool>false</bool>
+        </property>
+        <property name="buddy">
+         <cstring>kcfg_GpodderUsername</cstring>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="0">
+       <widget class="QLabel" name="labelPassword">
+        <property name="text">
+         <string>&Password:</string>
+        </property>
+        <property name="wordWrap">
+         <bool>false</bool>
+        </property>
+        <property name="buddy">
+         <cstring>kcfg_GpodderPassword</cstring>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="1">
+       <widget class="KLineEdit" name="kcfg_GpodderPassword">
+        <property name="echoMode">
+         <enum>QLineEdit::Password</enum>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <widget class="KLineEdit" name="kcfg_GpodderUsername"/>
+      </item>
+      <item row="4" column="0" colspan="2">
+       <widget class="QLabel" name="kActiveLabel1">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="text">
+         <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'Ubuntu'; font-size:9pt; font-weight:400; font-style:normal;">
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.gpodder.net/register/"><span style=" text-decoration: underline; color:#0057ae;">Sign up to gpodder.net</span></a></p></body></html></string>
+        </property>
+        <property name="wordWrap">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="1">
+       <widget class="QPushButton" name="testLogin">
+        <property name="text">
+         <string>&Test Login</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <widget class="Line" name="line">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <customwidgets>
+  <customwidget>
+   <class>KLineEdit</class>
+   <extends>QLineEdit</extends>
+   <header>klineedit.h</header>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>kcfg_GpodderUsername</tabstop>
+  <tabstop>kcfg_GpodderPassword</tabstop>
+  <tabstop>testLogin</tabstop>
+ </tabstops>
+ <includes>
+  <include location="local">klineedit.h</include>
+ </includes>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/services/gpodder/GpodderTreeItem.h b/src/services/gpodder/GpodderPodcastMeta.cpp
similarity index 54%
copy from src/services/gpodder/GpodderTreeItem.h
copy to src/services/gpodder/GpodderPodcastMeta.cpp
index 5698d5b..90bd38f 100644
--- a/src/services/gpodder/GpodderTreeItem.h
+++ b/src/services/gpodder/GpodderPodcastMeta.cpp
@@ -2,6 +2,7 @@
  * Copyright (c) 2011 Stefan Derkits <stefan at derkits.at>                                *
  * Copyright (c) 2011 Christian Wagner <christian.wagner86 at gmx.at>                      *
  * Copyright (c) 2011 Felix Winter <ixos01 at gmail.com>                                   *
+ * Copyright (c) 2011 Lucas Lira Gomes <x8lucas8x at gmail.com>                            *
  *                                                                                      *
  * 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        *
@@ -16,44 +17,47 @@
  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
  ****************************************************************************************/
 
-#ifndef GPODDERTREEITEM_H_
-#define GPODDERTREEITEM_H_
+#define DEBUG_PREFIX "GpodderPodcastMeta"
 
-#include <mygpo-qt/ApiRequest.h>
-#include <mygpo-qt/TagList.h>
+#include "GpodderPodcastMeta.h"
+#include "GpodderProvider.h"
 
-#include <QVariant>
-#include <QList>
-#include <QModelIndex>
+using namespace Podcasts;
 
-class GpodderServiceModel;
-
-class GpodderTreeItem : public QObject
+Podcasts::GpodderPodcastChannel::GpodderPodcastChannel( GpodderProvider *provider )
+    : PodcastChannel()
+    , m_provider( provider )
 {
-    Q_OBJECT
-public:
-    GpodderTreeItem( GpodderTreeItem *parent = 0 );
-    virtual ~GpodderTreeItem();
-
-    void appendChild( GpodderTreeItem *child );
-
-    GpodderTreeItem *child( int row );
-    int childCount() const;
-    void setHasChildren( bool hasChildren );
-    bool hasChildren() const;
-
-    GpodderTreeItem *parent() const;
-    bool isRoot() const;
+}
 
+Podcasts::GpodderPodcastChannel::GpodderPodcastChannel( GpodderProvider *provider,
+                                                        PodcastChannelPtr channel )
+    : PodcastChannel( channel )
+    , m_provider( provider )
+{
+}
 
-    virtual QVariant displayData() const;
+Podcasts::GpodderPodcastChannel::GpodderPodcastChannel( GpodderProvider *provider,
+                                                        mygpo::PodcastPtr channel )
+    : PodcastChannel()
+    , m_provider( provider )
+{
+    setUrl( channel->url() );
+    setWebLink( channel->website() );
+    setImageUrl( channel->logoUrl() );
+    setDescription( channel->description() );
+    setTitle( channel->title() );
+}
+
+Playlists::PlaylistProvider *
+Podcasts::GpodderPodcastChannel::provider() const
+{
+    return dynamic_cast<Playlists::PlaylistProvider *>( m_provider );
+}
 
-    virtual void appendTags( mygpo::TagListPtr tags/*, mygpo::ApiRequest& request, GpodderServiceModel *model*/ );
-    virtual void appendPodcasts( mygpo::PodcastListPtr podcasts );
-private:
-    QList<GpodderTreeItem*> m_childItems;
-    GpodderTreeItem *m_parentItem;
-    bool m_hasChildren;
-};
 
-#endif /* GPODDERTREEITEM_H_ */
+KUrl
+Podcasts::GpodderPodcastChannel::uidUrl() const
+{
+    return QString( "amarok-gpodder://%1" ).arg( url().url() );
+}
diff --git a/src/services/gpodder/GpodderTreeItem.h b/src/services/gpodder/GpodderPodcastMeta.h
similarity index 56%
copy from src/services/gpodder/GpodderTreeItem.h
copy to src/services/gpodder/GpodderPodcastMeta.h
index 5698d5b..0688eb0 100644
--- a/src/services/gpodder/GpodderTreeItem.h
+++ b/src/services/gpodder/GpodderPodcastMeta.h
@@ -2,6 +2,7 @@
  * Copyright (c) 2011 Stefan Derkits <stefan at derkits.at>                                *
  * Copyright (c) 2011 Christian Wagner <christian.wagner86 at gmx.at>                      *
  * Copyright (c) 2011 Felix Winter <ixos01 at gmail.com>                                   *
+ * Copyright (c) 2011 Lucas Lira Gomes <x8lucas8x at gmail.com>                            *
  *                                                                                      *
  * 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        *
@@ -16,44 +17,46 @@
  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
  ****************************************************************************************/
 
-#ifndef GPODDERTREEITEM_H_
-#define GPODDERTREEITEM_H_
+#ifndef GPODDERPODCASTMETA_H
+#define GPODDERPODCASTMETA_H
 
-#include <mygpo-qt/ApiRequest.h>
-#include <mygpo-qt/TagList.h>
+#include "core/podcasts/PodcastMeta.h"
+#include "core/playlists/PlaylistProvider.h"
 
-#include <QVariant>
-#include <QList>
-#include <QModelIndex>
+#include "ApiRequest.h"
 
-class GpodderServiceModel;
+namespace Podcasts {
 
-class GpodderTreeItem : public QObject
-{
-    Q_OBJECT
-public:
-    GpodderTreeItem( GpodderTreeItem *parent = 0 );
-    virtual ~GpodderTreeItem();
+class GpodderPodcastChannel;
+class GpodderProvider;
+
+typedef KSharedPtr<GpodderPodcastChannel> GpodderPodcastChannelPtr;
+typedef QList<GpodderPodcastChannelPtr> GpodderPodcastChannelList;
 
-    void appendChild( GpodderTreeItem *child );
+class GpodderPodcastChannel : public PodcastChannel
+{
+    public:
+        GpodderPodcastChannel( GpodderProvider *provider );
 
-    GpodderTreeItem *child( int row );
-    int childCount() const;
-    void setHasChildren( bool hasChildren );
-    bool hasChildren() const;
+        //Copy a PodcastChannel
+        GpodderPodcastChannel( GpodderProvider *provider, PodcastChannelPtr channel );
 
-    GpodderTreeItem *parent() const;
-    bool isRoot() const;
+        //Create a channel from a mygpo podcast channel
+        GpodderPodcastChannel( GpodderProvider *provider, mygpo::PodcastPtr channel );
 
+        //PodcastChannel Methods
+        virtual Playlists::PlaylistProvider *provider() const;
 
-    virtual QVariant displayData() const;
+        //Playlist virtual methods
+        virtual KUrl uidUrl() const;
 
-    virtual void appendTags( mygpo::TagListPtr tags/*, mygpo::ApiRequest& request, GpodderServiceModel *model*/ );
-    virtual void appendPodcasts( mygpo::PodcastListPtr podcasts );
-private:
-    QList<GpodderTreeItem*> m_childItems;
-    GpodderTreeItem *m_parentItem;
-    bool m_hasChildren;
+    private:
+        GpodderProvider *m_provider;
 };
 
-#endif /* GPODDERTREEITEM_H_ */
+}
+
+Q_DECLARE_METATYPE( Podcasts::GpodderPodcastChannelPtr )
+Q_DECLARE_METATYPE( Podcasts::GpodderPodcastChannelList )
+
+#endif
diff --git a/src/services/gpodder/GpodderProvider.cpp b/src/services/gpodder/GpodderProvider.cpp
new file mode 100644
index 0000000..2297907
--- /dev/null
+++ b/src/services/gpodder/GpodderProvider.cpp
@@ -0,0 +1,894 @@
+/****************************************************************************************
+ * Copyright (c) 2011 Stefan Derkits <stefan at derkits.at>                                *
+ * Copyright (c) 2011 Christian Wagner <christian.wagner86 at gmx.at>                      *
+ * Copyright (c) 2011 Felix Winter <ixos01 at gmail.com>                                   *
+ * Copyright (c) 2011 Lucas Lira Gomes <x8lucas8x at gmail.com>                            *
+ *                                                                                      *
+ * 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 <http://www.gnu.org/licenses/>.                           *
+ ****************************************************************************************/
+
+#define DEBUG_PREFIX "GpodderProvider"
+#define DEVICE_NAME "amarok"
+
+#include "GpodderProvider.h"
+
+#include "core/support/Debug.h"
+#include "EngineController.h"
+#include "core/support/Amarok.h"
+#include "core/support/Components.h"
+#include "core/interfaces/Logger.h"
+#include "NetworkAccessManagerProxy.h"
+#include "gpodder/GpodderServiceConfig.h"
+#include "core-impl/podcasts/sql/SqlPodcastMeta.h"
+#include "core-impl/capabilities/timecode/TimecodeWriteCapability.h"
+
+#include <QAction>
+#include <QLabel>
+#include <QTimer>
+
+#include <kio/job.h>
+#include <KLocale>
+#include <KVBox>
+
+using namespace Podcasts;
+
+GpodderProvider::GpodderProvider( const QString &username, const QString &password )
+    : m_nam( new QNetworkAccessManager( this ) )
+    , m_apiRequest( username, password, m_nam )
+    , m_username( username )
+    , m_channels()
+    , m_addRemoveResult()
+    , m_deviceUpdatesResult()
+    , m_episodeActionListResult()
+    , m_timestampStatus( 0 )
+    , m_timestampSubscription( subscriptionTimestamp() )
+    , m_removeAction( 0 )
+    , m_addList()
+    , m_removeList()
+    , m_timerSynchronizeStatus( new QTimer( this ) )
+    , m_timerSynchronizeSubscriptions( new QTimer( this ) )
+{
+    //Request all channels and episodes from DEVICE_NAME device and after it
+    //request episode actions too
+    requestDeviceUpdates();
+
+    //Add the provider for gpodder to the playlist manager
+    The::playlistManager()->addProvider( this,PlaylistManager::PodcastChannel );
+
+    //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(),
+             SIGNAL(playlistAdded( Playlists::PlaylistPtr )),
+             SLOT(slotSyncPlaylistAdded( Playlists::PlaylistPtr )) );
+    connect( The::playlistManager()->defaultPodcasts(),
+             SIGNAL(playlistRemoved(Playlists::PlaylistPtr )),
+             SLOT(slotSyncPlaylistRemoved(Playlists::PlaylistPtr )) );
+
+    //Connect engine controller signals to make possible to synchronize podcast status
+    connect( The::engineController(), SIGNAL(trackChanged( Meta::TrackPtr )),
+             SLOT(slotTrackChanged( Meta::TrackPtr )) );
+
+    connect( The::engineController(), SIGNAL(trackPositionChanged( qint64, bool )),
+             SLOT(slotTrackPositionChanged( qint64, bool )) );
+
+    //These timers will periodically synchronize data between local podcasts and gpodder.net
+    connect( m_timerSynchronizeStatus, SIGNAL(timeout()), SLOT(timerSynchronizeStatus()) );
+    connect( m_timerSynchronizeSubscriptions, SIGNAL(timeout()),
+             SLOT(timerSynchronizeSubscriptions()) );
+
+    m_timerSynchronizeStatus->stop();
+    m_timerSynchronizeSubscriptions->start( 1000 * 60 );
+}
+
+GpodderProvider::~GpodderProvider()
+{
+    delete m_timerSynchronizeStatus;
+    delete m_timerSynchronizeSubscriptions;
+
+    //Send remaining subscriptions changes
+    if( !m_removeList.isEmpty() || !m_addList.isEmpty() )
+    {
+        m_addRemoveResult = m_apiRequest.addRemoveSubscriptions( m_username, DEVICE_NAME, m_addList,
+                                                                 m_removeList );
+        m_addList.clear();
+        m_removeList.clear();
+    }
+
+    //Send remaining status changes
+    if( !m_uploadEpisodeStatusMap.isEmpty() )
+    {
+        m_episodeActionsResult =
+                m_apiRequest.uploadEpisodeActions( m_username, m_uploadEpisodeStatusMap.values() );
+        m_uploadEpisodeStatusMap.clear();
+    }
+
+    m_episodeStatusMap.clear();
+
+    m_trackToSyncStatus = NULL;
+
+    //Remove the provider
+    The::playlistManager()->removeProvider( this );
+}
+
+bool
+GpodderProvider::possiblyContainsTrack( const KUrl &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 KUrl &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 KUrl &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 ) )
+        {
+            //With this the service will try to subscribe slave in gpodder.net
+            m_addList << QUrl( slave->url().url() );
+        }
+    }
+
+    //Create a playlist synchronisation between master and slave
+    The::playlistManager()->setupSync( Playlists::PlaylistPtr::dynamicCast( master ),
+                                       Playlists::PlaylistPtr::dynamicCast( slave )
+                                       );
+
+    return Playlists::PlaylistPtr::dynamicCast( slave );
+}
+
+
+PodcastChannelPtr
+GpodderProvider::addChannel( 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" );
+}
+
+KIcon
+GpodderProvider::icon() const
+{
+    return KIcon( "view-services-gpodder-amarok" );
+}
+
+Playlists::PlaylistList
+GpodderProvider::playlists()
+{
+    Playlists::PlaylistList playlist;
+
+    foreach( PodcastChannelPtr channel, m_channels )
+        playlist << Playlists::PlaylistPtr::staticCast( channel );
+
+    return playlist;
+}
+
+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 )
+        {
+            QUrl url = QUrl( m_channels.at( i )->url().url() );
+
+            m_channels.removeAt( i );
+            m_removeList << url;
+            m_episodeStatusMap.remove( url );
+            m_uploadEpisodeStatusMap.remove( url );
+
+            return;
+        }
+    }
+}
+
+QList<QAction *>
+GpodderProvider::channelActions( PodcastChannelList channels )
+{
+    DEBUG_BLOCK
+
+    QList<QAction *> actions;
+
+    if( m_removeAction == 0 )
+    {
+        m_removeAction = new QAction(
+            KIcon( "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
+    PodcastChannelList actionList =
+        m_removeAction->data().value<PodcastChannelList>();
+
+    actionList << channels;
+    m_removeAction->setData( QVariant::fromValue( actionList ) );
+
+    actions << m_removeAction;
+
+    return actions;
+}
+
+QList<QAction *>
+GpodderProvider::playlistActions( Playlists::PlaylistPtr playlist )
+{
+    DEBUG_BLOCK
+
+    PodcastChannelList channels;
+    PodcastChannelPtr channel = PodcastChannelPtr::dynamicCast( playlist );
+
+    if( channel.isNull() )
+        return QList<QAction *>();
+
+    return channelActions( channels << channel );
+}
+
+void
+GpodderProvider::slotRemoveChannels()
+{
+    DEBUG_BLOCK
+
+    QAction *action = qobject_cast<QAction *>( QObject::sender() );
+
+    if( action == 0 )
+        return;
+
+    PodcastChannelList channels = action->data().value<PodcastChannelList>();
+    action->setData( QVariant() );      //Clear data
+
+    foreach( PodcastChannelPtr channel, channels )
+    {
+        removeChannel( channel->url().url() );
+        emit playlistRemoved( Playlists::PlaylistPtr::dynamicCast( channel ) );
+    }
+}
+
+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 );
+}
+
+void
+GpodderProvider::slotSyncPlaylistRemoved( Playlists::PlaylistPtr playlist )
+{
+    Podcasts::PodcastChannelPtr channel = Podcasts::PodcastChannelPtr::dynamicCast( playlist );
+    //If gpodder channels doesn't contais 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().url() );
+            return;
+        }
+}
+
+qulonglong
+GpodderProvider::subscriptionTimestamp()
+{
+    KConfigGroup config = KGlobal::config()->group( GpodderServiceConfig::configSectionName() );
+    return config.readEntry( "subscriptionTimestamp", 0 );
+}
+
+void
+GpodderProvider::setSubscriptionTimestamp( qulonglong newTimestamp )
+{
+    KConfigGroup config = KGlobal::config()->group( GpodderServiceConfig::configSectionName() );
+    config.writeEntry( "subscriptionTimestamp", newTimestamp );
+}
+
+void
+GpodderProvider::synchronizeStatus()
+{
+    DEBUG_BLOCK
+
+    debug() << "new episodes status: " << m_uploadEpisodeStatusMap.size();
+
+    if( !m_uploadEpisodeStatusMap.isEmpty() )
+    {
+        m_episodeActionsResult =
+                m_apiRequest.uploadEpisodeActions( m_username, m_uploadEpisodeStatusMap.values() );
+
+        //Only clear m_episodeStatusList if the synchronisation with gpodder.net really worked
+        connect( m_episodeActionsResult.data(), SIGNAL( finished() ), this,
+                 SLOT( slotSuccessfulStatusSynchronisation() ) );
+    }
+
+    Amarok::Components::logger()->shortMessage( i18n( "Trying to synchronize with gpodder.net" ) );
+}
+
+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::synchronizeSubscriptions()
+{
+    DEBUG_BLOCK
+
+    debug() << "add: " << m_addList.size();
+    debug() << "remove: " << m_removeList.size();
+
+    if( !m_removeList.isEmpty() || !m_addList.isEmpty() )
+    {
+        m_addRemoveResult =
+                m_apiRequest.addRemoveSubscriptions( m_username, DEVICE_NAME, m_addList, m_removeList );
+
+        //Only clear m_addList and m_removeList if the synchronisation with gpodder.net really worked
+        connect( m_addRemoveResult.data(), SIGNAL( finished() ), this,
+                 SLOT( slotSuccessfulSubscriptionSynchronisation() ) );
+    }
+
+    Amarok::Components::logger()->shortMessage( i18n( "Trying to synchronize with gpodder.net" ) );
+}
+
+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::timerSynchronizeSubscriptions()
+{
+    synchronizeSubscriptions();
+}
+
+void
+GpodderProvider::timerSynchronizeStatus()
+{
+    synchronizeStatus();
+}
+
+void
+GpodderProvider::slotTrackChanged( Meta::TrackPtr track )
+{
+    m_trackToSyncStatus = NULL;
+
+    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( track->uidUrl() ) ) ||
+                ( this->possiblyContainsTrack( track->uidUrl() ) &&
+                  The::playlistManager()->defaultPodcasts()->possiblyContainsTrack( track->uidUrl() )
+                  ) )
+        {
+            m_trackToSyncStatus = track;
+
+            QTimer::singleShot( 10 * 1000, this, SLOT( timerPrepareToSyncPodcastStatus() ) );
+
+            //A bookmark will be created if we have a play status available,
+            //for current track, at m_episodeStatusMap
+            createPlayStatusBookmark();
+        }
+        else
+        {
+            //Stop the timer if track is not from a podcast subscribed at gpodder.net
+            m_timerSynchronizeStatus->stop();
+        }
+    }
+    else
+    {
+        //Stop the timer if Amarok is stopped
+        m_timerSynchronizeStatus->stop();
+    }
+}
+
+void
+GpodderProvider::slotTrackPositionChanged( qint64 position, bool userSeek )
+{
+    //If the current track is in one of the subscribed gpodder channels and it's position
+    //is not at the beggining 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( 10 * 1000, this, SLOT( timerPrepareToSyncPodcastStatus() ) );
+        }
+        else
+        {
+            //Synchronize episode actions every 30 seconds
+            if( position % 30 )
+            {
+                EpisodeActionPtr tempEpisodeAction;
+                PodcastEpisodePtr tempEpisode = PodcastEpisodePtr::dynamicCast( m_trackToSyncStatus );
+
+                if( tempEpisode )
+                {
+                    qulonglong positionSeconds = The::engineController()->trackPosition();
+                    qulonglong lengthSeconds = The::engineController()->trackLength() / 1000;
+
+                    tempEpisodeAction = EpisodeActionPtr(
+                                new EpisodeAction( QUrl( tempEpisode->channel()->url().url() ),
+                                                   QUrl( tempEpisode->uidUrl() ),
+                                                   DEVICE_NAME,
+                                                   EpisodeAction::Play,
+                                                   m_timestampStatus,
+                                                   1,
+                                                   positionSeconds,
+                                                   lengthSeconds
+                                                  ) );
+
+                    //Any previous episodeAction, from the same podcast, will be replaced
+                    m_uploadEpisodeStatusMap.insert( tempEpisode->uidUrl(), tempEpisodeAction );
+                    //Make local podcasts aware of new episodeActions
+                    m_episodeStatusMap.insert( tempEpisode->uidUrl(), tempEpisodeAction );
+                }
+            }
+        }
+    }
+}
+
+void
+GpodderProvider::timerPrepareToSyncPodcastStatus()
+{
+    if( The::engineController()->currentTrack() == m_trackToSyncStatus )
+    {
+        EpisodeActionPtr tempEpisodeAction;
+        PodcastEpisodePtr tempEpisode = PodcastEpisodePtr::dynamicCast( m_trackToSyncStatus );
+
+        if( tempEpisode )
+        {
+            qulonglong position = The::engineController()->trackPosition();
+
+            tempEpisodeAction = EpisodeActionPtr(
+                        new EpisodeAction( QUrl( tempEpisode->channel()->url().url() ),
+                                           QUrl( tempEpisode->uidUrl() ),
+                                           DEVICE_NAME,
+                                           EpisodeAction::Play,
+                                           m_timestampStatus,
+                                           0, position,
+                                           m_trackToSyncStatus->length()
+                                          ) );
+
+            //Any previous episodeAction, from the same podcast, will be replaced
+            m_uploadEpisodeStatusMap.insert( tempEpisode->uidUrl(), tempEpisodeAction );
+        }
+
+        //Starts or restarts the timer
+        m_timerSynchronizeStatus->start( 60 * 1000 );
+    }
+}
+
+void
+GpodderProvider::deviceUpdatesFinished()
+{
+    DEBUG_BLOCK
+
+    //DeviceUpdates contain all channel adds/removes and episode updates since timestamp.
+    foreach( mygpo::PodcastPtr podcast, m_deviceUpdatesResult->addList() )
+    {
+        debug() << "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 );
+    }
+
+    //Only after all subscription changes are committed should we save the timestamp
+    setSubscriptionTimestamp( m_deviceUpdatesResult->timestamp() );
+}
+
+void
+GpodderProvider::continueDeviceUpdatesFinished()
+{
+    foreach( GpodderPodcastChannelPtr channel, m_channelsToAdd )
+    {
+        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 synchronisation between master and slave
+        The::playlistManager()->setupSync( Playlists::PlaylistPtr::dynamicCast( master ),
+                                           Playlists::PlaylistPtr::dynamicCast( slave )
+                                           );
+    }
+
+    m_channelsToAdd.clear();
+
+    //Request the last episode status for every episode in gpodder.net subscribed podcasts
+    requestEpisodeActionsInCascade();
+}
+
+void
+GpodderProvider::deviceUpdatesParseError()
+{
+    DEBUG_BLOCK
+
+    QTimer::singleShot( 10000, this, SLOT( requestDeviceUpdates() ) );
+
+    debug() << "deviceUpdates [Subscription Synchronisation] - Parse error";
+}
+
+void
+GpodderProvider::deviceUpdatesRequestError( QNetworkReply::NetworkError error )
+{
+    DEBUG_BLOCK
+
+    QTimer::singleShot( 10000, this, SLOT( requestDeviceUpdates() ) );
+
+    debug() << "deviceUpdates [Subscription Synchronisation] - Request error nr.: " << error;
+}
+
+void
+GpodderProvider::episodeActionsInCascadeFinished()
+{
+    DEBUG_BLOCK
+
+    m_timestampStatus = QDateTime::currentMSecsSinceEpoch();
+
+    foreach( EpisodeActionPtr tempEpisodeAction, m_episodeActionListResult->list() )
+    {
+        if( tempEpisodeAction->action() == EpisodeAction::Play )
+        {
+            debug() << "Adding a new play status to episode " << tempEpisodeAction->episodeUrl();
+            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 if( tempEpisodeAction->action() == EpisodeAction::New )
+        {
+            debug() << "Adding a new episode " << tempEpisodeAction->episodeUrl();
+            m_episodeStatusMap.insert( tempEpisodeAction->episodeUrl(), tempEpisodeAction );
+
+            foreach( PodcastChannelPtr channel, m_channels )
+            {
+                if( channel->url() == tempEpisodeAction->podcastUrl() )
+                {
+                    PodcastEpisodePtr tempEpisode;
+
+                    tempEpisode = PodcastEpisodePtr();
+                    tempEpisode->setUidUrl( tempEpisodeAction->episodeUrl() );
+                    tempEpisode->setChannel( PodcastChannelPtr::dynamicCast( channel ) );
+
+                    channel->addEpisode( tempEpisode );
+                }
+            }
+        }
+    }
+
+    //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( 10 * 1000, 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 Synchronisation] - Parse Error";
+}
+
+void
+GpodderProvider::episodeActionsInCascadeRequestError( QNetworkReply::NetworkError error )
+{
+    DEBUG_BLOCK
+
+    QTimer::singleShot( 10 * 1000, 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 Synchronisation] - Request error nr.: " << error;
+}
+
+void
+GpodderProvider::updateLocalPodcasts( const QList<QPair<QUrl,QUrl> > updatedUrls )
+{
+    QList< QPair<QUrl,QUrl> >::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::requestDeviceUpdates()
+{
+    m_deviceUpdatesResult = m_apiRequest.deviceUpdates( m_username, DEVICE_NAME, 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::requestEpisodeActionsInCascade()
+{
+    DEBUG_BLOCK
+
+    //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()) );
+    }
+}
+
+void
+GpodderProvider::createPlayStatusBookmark()
+{
+    Meta::TrackPtr track = The::engineController()->currentTrack();
+
+    if( track )
+    {
+        EpisodeActionPtr tempEpisodeAction = m_episodeStatusMap.value( 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->hasCapabilityInterface( Capabilities::Capability::WriteTimecode ) )
+            {
+                QScopedPointer<Capabilities::TimecodeWriteCapability> tcw( track->create<Capabilities::TimecodeWriteCapability>() );
+                qint64 positionMiliSeconds = tempEpisodeAction->position() * 1000;
+
+                tcw->writeAutoTimecode( positionMiliSeconds );
+            }
+        }
+    }
+}
+
+void
+GpodderProvider::urlResolvePermanentRedirection( KIO::Job *job, const KUrl &fromUrl,
+        const KUrl &toUrl )
+{
+    DEBUG_BLOCK
+
+    KIO::TransferJob *transferJob = dynamic_cast<KIO::TransferJob *>( job );
+    GpodderPodcastChannelPtr channel = m_resolvedPodcasts.value( transferJob );
+    channel->setUrl( toUrl );
+
+    debug() << fromUrl.url() << " was redirected to " << toUrl.url();
+}
+
+void
+GpodderProvider::urlResolveFinished( KJob *job )
+{
+    KIO::TransferJob *transferJob = dynamic_cast<KIO::TransferJob *>( job );
+
+    if( transferJob && ( !( transferJob->isErrorPage() || job->error() ) ) )
+    {
+        m_channelsToAdd.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::requestUrlResolve( Podcasts::GpodderPodcastChannelPtr channel )
+{
+    if( !channel )
+        return;
+
+    m_resolveUrlJob = KIO::get( channel->url(), KIO::Reload, KIO::HideProgressInfo );
+
+    connect( m_resolveUrlJob, SIGNAL(result( KJob * )),
+             SLOT(urlResolveFinished( KJob * )) );
+    connect( m_resolveUrlJob,
+             SIGNAL(permanentRedirection( KIO::Job *, const KUrl &, const KUrl & )),
+             SLOT(urlResolvePermanentRedirection( KIO::Job *, const KUrl &, const KUrl & )) );
+
+    m_resolvedPodcasts.insert( m_resolveUrlJob, channel );
+}
diff --git a/src/services/gpodder/GpodderProvider.h b/src/services/gpodder/GpodderProvider.h
new file mode 100644
index 0000000..e9dbe4c
--- /dev/null
+++ b/src/services/gpodder/GpodderProvider.h
@@ -0,0 +1,163 @@
+/****************************************************************************************
+ * Copyright (c) 2011 Stefan Derkits <stefan at derkits.at>                                *
+ * Copyright (c) 2011 Christian Wagner <christian.wagner86 at gmx.at>                      *
+ * Copyright (c) 2011 Felix Winter <ixos01 at gmail.com>                                   *
+ * Copyright (c) 2011 Lucas Lira Gomes <x8lucas8x at gmail.com>                            *
+ *                                                                                      *
+ * 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 <http://www.gnu.org/licenses/>.                           *
+ ****************************************************************************************/
+
+#ifndef GPODDERPODCASTPROVIDER_H
+#define GPODDERPODCASTPROVIDER_H
+
+#include "core/podcasts/PodcastProvider.h"
+
+#include <mygpo-qt/ApiRequest.h>
+#include <mygpo-qt/EpisodeActionList.h>
+#include "core/podcasts/PodcastReader.h"
+#include "playlistmanager/PlaylistManager.h"
+#include "playlistmanager/file/KConfigSyncRelStore.h"
+
+#include "GpodderPodcastMeta.h"
+
+#include <QCheckBox>
+#include <QQueue>
+#include <QPair>
+
+#include <KLocale>
+#include <KDialog>
+
+using namespace mygpo;
+
+class QAction;
+
+namespace Podcasts
+{
+
+class GpodderProvider : public PodcastProvider
+{
+    Q_OBJECT
+public:
+    GpodderProvider( const QString &username, const QString &password );
+    ~GpodderProvider();
+
+    bool possiblyContainsTrack( const KUrl &url ) const;
+    Meta::TrackPtr trackForUrl( const KUrl &url );
+    /** Special function to get an episode for a given guid.
+     *
+     * note: this functions is required because KUrl does not preserve every possible guids.
+     * This means we can not use trackForUrl().
+     * Problematic guids contain non-latin characters, percent encoded parts, capitals, etc.
+     */
+    virtual PodcastEpisodePtr episodeForGuid( const QString &guid );
+
+    virtual void addPodcast( const KUrl &url );
+
+    virtual Podcasts::PodcastChannelPtr addChannel( Podcasts::PodcastChannelPtr channel );
+    virtual Podcasts::PodcastEpisodePtr addEpisode( Podcasts::PodcastEpisodePtr episode );
+
+    virtual Podcasts::PodcastChannelList channels();
+
+    // PlaylistProvider methods
+    virtual QString prettyName() const;
+    virtual KIcon icon() const;
+    virtual Playlists::PlaylistList playlists();
+    virtual void completePodcastDownloads();
+
+    /** Copy a playlist to the provider.
+    */
+    virtual Playlists::PlaylistPtr addPlaylist( Playlists::PlaylistPtr playlist );
+    QList<QAction *> channelActions( PodcastChannelList episodes );
+    QList<QAction *> playlistActions( Playlists::PlaylistPtr playlist );
+    
+signals:
+    //PlaylistProvider signals
+    void updated();
+    void playlistAdded( Playlists::PlaylistPtr playlist );
+    void playlistRemoved( Playlists::PlaylistPtr playlist );
+
+private slots:
+    void requestDeviceUpdates();
+    void deviceUpdatesFinished();
+    void continueDeviceUpdatesFinished();
+    void deviceUpdatesParseError();
+    void deviceUpdatesRequestError( QNetworkReply::NetworkError error );
+
+    void requestEpisodeActionsInCascade();
+    void episodeActionsInCascadeFinished();
+    void episodeActionsInCascadeParseError();
+    void episodeActionsInCascadeRequestError( QNetworkReply::NetworkError error );
+
+    void timerSynchronizeStatus();
+    void timerSynchronizeSubscriptions();
+    void timerPrepareToSyncPodcastStatus();
+
+    void slotRemoveChannels();
+    void slotSuccessfulStatusSynchronisation();
+    void slotSuccessfulSubscriptionSynchronisation();
+
+    void slotSyncPlaylistAdded( Playlists::PlaylistPtr playlist );
+    void slotSyncPlaylistRemoved( Playlists::PlaylistPtr playlist );
+
+    void slotTrackChanged( Meta::TrackPtr track );
+    void slotTrackPositionChanged( qint64 position, bool userSeek );
+
+    void requestUrlResolve( GpodderPodcastChannelPtr channel );
+    void urlResolvePermanentRedirection ( KIO::Job *job, const KUrl &fromUrl,
+            const KUrl &toUrl );
+    void urlResolveFinished( KJob * );
+
+private:
+    QNetworkAccessManager *m_nam;
+    ApiRequest m_apiRequest;
+    const QString m_username;
+    PodcastChannelList m_channels;
+    KIO::TransferJob *m_resolveUrlJob;
+
+    AddRemoveResultPtr m_addRemoveResult;
+    DeviceUpdatesPtr m_deviceUpdatesResult;
+    AddRemoveResultPtr m_episodeActionsResult;
+    EpisodeActionListPtr m_episodeActionListResult;
+
+    qulonglong m_timestampStatus;
+    qulonglong m_timestampSubscription;
+
+    qulonglong subscriptionTimestamp();
+    void setSubscriptionTimestamp( qulonglong newTimestamp );
+    
+    void removeChannel( const QUrl &url );
+    void createPlayStatusBookmark();
+
+    void synchronizeStatus();
+    void synchronizeSubscriptions();
+    void updateLocalPodcasts( const QList< QPair<QUrl,QUrl> > updatedUrls );
+
+    QAction *m_removeAction; //remove a subscription
+    QList<QUrl> m_addList;
+    QList<QUrl> m_removeList;
+
+    QQueue<QUrl> m_channelsToRequestActions;
+    QQueue<GpodderPodcastChannelPtr> m_channelsToAdd;
+    QMap<QUrl,EpisodeActionPtr> m_episodeStatusMap;
+    QMap<QUrl,EpisodeActionPtr> m_uploadEpisodeStatusMap;
+    QMap<KIO::TransferJob *,GpodderPodcastChannelPtr> m_resolvedPodcasts;
+
+    QTimer *m_timerSynchronizeStatus;
+    QTimer *m_timerSynchronizeSubscriptions;
+
+    Meta::TrackPtr m_trackToSyncStatus;
+};
+
+}
+
+#endif
diff --git a/src/services/gpodder/GpodderService.cpp b/src/services/gpodder/GpodderService.cpp
index e0cb02f..ed029ba 100644
--- a/src/services/gpodder/GpodderService.cpp
+++ b/src/services/gpodder/GpodderService.cpp
@@ -17,15 +17,16 @@
  ****************************************************************************************/
 
 #define DEBUG_PREFIX "GpodderService"
-#include "core/support/Debug.h"
 
 #include "GpodderService.h"
 
+#include "GpodderServiceConfig.h"
 #include "GpodderServiceView.h"
 #include "GpodderServiceModel.h"
 #include "GpodderPodcastTreeItem.h"
 #include "GpodderSortFilterProxyModel.h"
 
+#include "core/support/Debug.h"
 #include "core/podcasts/PodcastProvider.h"
 #include "playlistmanager/PlaylistManager.h"
 #include "widgets/SearchWidget.h"
@@ -43,12 +44,13 @@ AMAROK_EXPORT_SERVICE_PLUGIN( gpodder, GpodderServiceFactory )
 GpodderServiceFactory::GpodderServiceFactory( QObject *parent, const QVariantList &args )
     : ServiceFactory( parent, args )
 {
-    KPluginInfo pluginInfo(  "amarok_service_gpodder.desktop", "services" );
+    KPluginInfo pluginInfo( "amarok_service_gpodder.desktop", "services" );
     pluginInfo.setConfig( config() );
     m_info = pluginInfo;
 }
 
-void GpodderServiceFactory::init()
+void
+GpodderServiceFactory::init()
 {
     ServiceBase *service = createGpodderService();
     if( service )
@@ -59,26 +61,31 @@ void GpodderServiceFactory::init()
     }
 }
 
-QString GpodderServiceFactory::name()
+QString
+GpodderServiceFactory::name()
 {
     return "gpodder.net";
 }
 
-KPluginInfo GpodderServiceFactory::info()
+KPluginInfo
+GpodderServiceFactory::info()
 {
     KPluginInfo pluginInfo( "amarok_service_gpodder.desktop", "services" );
     pluginInfo.setConfig( config() );
     return pluginInfo;
 }
 
-KConfigGroup GpodderServiceFactory::config()
+KConfigGroup
+GpodderServiceFactory::config()
 {
-    return Amarok::config( "Service_gpodder" );
+    return Amarok::config( GpodderServiceConfig::configSectionName() );
 }
 
-void GpodderServiceFactory::slotCreateGpodderService()
+void
+GpodderServiceFactory::slotCreateGpodderService()
 {
-    if( !m_initialized )  // Until we can remove a service when networking gets disabled, only create it the first time.
+    //Until we can remove a service when networking gets disabled, only create it the first time.
+    if( !m_initialized )
     {
         ServiceBase *service = createGpodderService();
         if( service )
@@ -90,7 +97,8 @@ void GpodderServiceFactory::slotCreateGpodderService()
     }
 }
 
-void GpodderServiceFactory::slotRemoveGpodderService()
+void
+GpodderServiceFactory::slotRemoveGpodderService()
 {
     if( m_activeServices.size() == 0 )
         return;
@@ -100,19 +108,27 @@ void GpodderServiceFactory::slotRemoveGpodderService()
     m_activeServices.clear();
 }
 
-ServiceBase* GpodderServiceFactory::createGpodderService()
+ServiceBase *
+GpodderServiceFactory::createGpodderService()
 {
-    ServiceBase* service = new GpodderService( this, QLatin1String( "gpodder" ) );
+    ServiceBase *service = new GpodderService( this, QLatin1String( "gpodder" ) );
     return service;
 }
 
-GpodderService::GpodderService( GpodderServiceFactory* parent, const QString& name )
-    : ServiceBase( name, parent, false ), m_inited( false )
+GpodderService::GpodderService( GpodderServiceFactory *parent, const QString &name )
+    : ServiceBase( name, parent, false )
+    , m_inited( false )
+    , m_podcastProvider( 0 )
+    , m_proxyModel( 0 )
+    , m_subscribeButton( 0 )
+    , m_selectionModel( 0 )
 {
     DEBUG_BLOCK
+
     setShortDescription( i18n( "gpodder.net: Podcast Directory Service" ) );
     setIcon( KIcon( "view-services-gpodder-amarok" ) );
-    setLongDescription( i18n( "gpodder.net is an online Podcast Directory & Synchonisation Service." ) );
+    setLongDescription(
+                i18n( "gpodder.net is an online Podcast Directory & Synchonisation Service." ) );
     setImagePath( KStandardDirs::locate( "data", "amarok/images/mygpo.png" ) );
 
     init();
@@ -122,21 +138,27 @@ GpodderService::~GpodderService()
 {
     DEBUG_BLOCK
 
+    delete m_podcastProvider;
 }
 
 //This Method should only contain the most necessary things for initilazing the Service
-void GpodderService::init()
+void
+GpodderService::init()
 {
+    GpodderServiceConfig config;
 
-    m_serviceready = true;
+    if( config.enableProvider() )
+        enableGpodderProvider( config.username(), config.password() );
 
+    m_serviceready = 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()
+//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();
@@ -146,7 +168,7 @@ void GpodderService::polish()
     //do not allow this content to get added to the playlist. At least not for now
     setPlayableTracks( false );
 
-    GpodderServiceView* view = new GpodderServiceView( this );
+    GpodderServiceView *view = new GpodderServiceView( this );
     view->setHeaderHidden( true );
     view->setFrameShape( QFrame::NoFrame );
 
@@ -182,30 +204,41 @@ void GpodderService::polish()
 
     connect( m_subscribeButton, SIGNAL( clicked() ), this, SLOT( subscribe() ) );
 
-
-
     connect( m_searchWidget, SIGNAL( filterChanged( const QString & ) ),
              m_proxyModel, SLOT( setFilterWildcard( const QString & ) ) );
 
     m_polished = true;
 }
 
-void GpodderService::itemSelected( CollectionTreeItem * selectedItem )
+void
+GpodderService::itemSelected( CollectionTreeItem * selectedItem )
 {
     Q_UNUSED( selectedItem )
     DEBUG_BLOCK
     return;
 }
 
-void GpodderService::subscribe()
+void
+GpodderService::subscribe()
 {
     QModelIndex index = m_proxyModel->mapToSource( m_selectionModel->currentIndex() );
     GpodderTreeItem *treeItem = static_cast<GpodderTreeItem*>( index.internalPointer() );
 
-    if( GpodderPodcastTreeItem *podcastTreeItem = qobject_cast<GpodderPodcastTreeItem*>( treeItem ) )
+    if( GpodderPodcastTreeItem *pcastTreeItem = qobject_cast<GpodderPodcastTreeItem*>( treeItem ) )
     {
         Podcasts::PodcastProvider *podcastProvider = The::playlistManager()->defaultPodcasts();
-        KUrl kUrl( podcastTreeItem->podcast()->url() );
+        KUrl kUrl( pcastTreeItem->podcast()->url() );
         podcastProvider->addPodcast( kUrl );
     }
 }
+
+void
+GpodderService::enableGpodderProvider(const QString &username, const QString &password)
+{
+    DEBUG_BLOCK
+
+    debug() << "enabling GpodderProvider";
+
+    delete m_podcastProvider;
+    m_podcastProvider = new Podcasts::GpodderProvider( username, password );
+}
diff --git a/src/services/gpodder/GpodderService.h b/src/services/gpodder/GpodderService.h
index fac508a..e72d832 100644
--- a/src/services/gpodder/GpodderService.h
+++ b/src/services/gpodder/GpodderService.h
@@ -1,7 +1,7 @@
 /****************************************************************************************
- * Copyright (c) 2010 Stefan Derkits <stefan at derkits.at>                                *
- * Copyright (c) 2010 Christian Wagner <christian.wagner86 at gmx.at>                      *
- * Copyright (c) 2010 Felix Winter <ixos01 at gmail.com>                                   *
+ * Copyright (c) 2010 - 2011 Stefan Derkits <stefan at derkits.at>                         *
+ * Copyright (c) 2010 - 2011 Christian Wagner <christian.wagner86 at gmx.at>               *
+ * Copyright (c) 2010 - 2011 Felix Winter <ixos01 at gmail.com>                            *
  *                                                                                      *
  * 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        *
@@ -19,6 +19,8 @@
 #ifndef GPODDERSERVICE_H
 #define GPODDERSERVICE_H
 
+#include "GpodderProvider.h"
+
 #include "core/support/Amarok.h"
 #include "services/ServiceBase.h"
 
@@ -29,7 +31,7 @@ class GpodderService;
 
 namespace The
 {
-GpodderService* gpodderService();
+    GpodderService *gpodderService();
 }
 
 class GpodderServiceFactory : public ServiceFactory
@@ -50,7 +52,7 @@ private slots:
     void slotRemoveGpodderService();
 
 private:
-    ServiceBase* createGpodderService();
+    ServiceBase *createGpodderService();
 };
 
 class GpodderService : public ServiceBase
@@ -58,11 +60,10 @@ class GpodderService : public ServiceBase
     Q_OBJECT
 
 public:
-    GpodderService( GpodderServiceFactory* parent, const QString& name );
+    GpodderService( GpodderServiceFactory *parent, const QString &name );
     virtual ~GpodderService();
 
 private slots:
-
     void subscribe();
     void itemSelected( CollectionTreeItem *selectedItem );
 
@@ -70,13 +71,14 @@ private:
     void init();
     void polish();
 
-    virtual Collections::Collection * collection()
-    {
-        return 0;
-    }
+    void enableGpodderProvider( const QString &username, const QString &password );
+
+    virtual Collections::Collection *collection() { return 0; }
 
     bool m_inited;
 
+    Podcasts::GpodderProvider *m_podcastProvider;
+
     QSortFilterProxyModel *m_proxyModel;
 
     QPushButton *m_subscribeButton;
diff --git a/src/services/gpodder/GpodderServiceConfig.cpp b/src/services/gpodder/GpodderServiceConfig.cpp
new file mode 100644
index 0000000..5ff6dea
--- /dev/null
+++ b/src/services/gpodder/GpodderServiceConfig.cpp
@@ -0,0 +1,195 @@
+/****************************************************************************************
+ * Copyright (c) 2007 Shane King <kde at dontletsstart.com>                                *
+ * Copyright (c) 2009 Leo Franchi <lfranchi at kde.org>                                    *
+ * Copyright (c) 2010 Stefan Derkits <stefan at derkits.at>                                *
+ * Copyright (c) 2010 Christian Wagner <christian.wagner86 at gmx.at>                      *
+ * Copyright (c) 2010 Felix Winter <ixos01 at gmail.com>                                   *
+ *                                                                                      *
+ * 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 <http://www.gnu.org/licenses/>.                           *
+ ****************************************************************************************/
+
+#define DEBUG_PREFIX "GPodderConfig"
+
+#include "App.h"
+#include "GpodderServiceConfig.h"
+#include "core/support/Debug.h"
+
+#include <KWallet/Wallet>
+#include <KDialog>
+#include <QLabel>
+
+GpodderServiceConfig::GpodderServiceConfig()
+    : m_askDiag( 0 )
+    , m_wallet( 0 )
+{
+    DEBUG_BLOCK
+    
+    KConfigGroup config = KGlobal::config()->group( configSectionName() );
+
+    // we only want to load the wallet if the user has enabled features that require a user/pass
+    bool synchronise = config.readEntry( "synchronise", false );
+    
+    if( synchronise )
+    {
+        // open wallet unless explicitly told not to
+        if( !( config.readEntry( "ignoreWallet", QString() ) == "yes" ) )
+            m_wallet = KWallet::Wallet::openWallet( KWallet::Wallet::NetworkWallet(), 0, KWallet::Wallet::Synchronous );
+    }
+
+    load();
+}
+
+
+GpodderServiceConfig::~GpodderServiceConfig()
+{
+    DEBUG_BLOCK
+
+    if( m_askDiag )
+        m_askDiag->deleteLater();
+
+    if( m_wallet )
+        m_wallet->deleteLater();
+}
+
+
+void
+GpodderServiceConfig::load()
+{
+    debug() << "Load config";
+
+    KConfigGroup config = KGlobal::config()->group( configSectionName() );
+
+    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!";
+
+        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( config.readEntry( "ignoreWallet", QString() ) != "no" )
+    {
+        m_username = config.readEntry( "username", QString() );
+        m_password = config.readEntry( "password", QString() );
+    }
+    
+    m_enableProvider = config.readEntry( "enableProvider", false );
+    m_synchronise = config.readEntry( "synchronise", false );
+}
+
+
+void
+GpodderServiceConfig::save()
+{
+    debug() << "Save config";
+
+    KConfigGroup config = KGlobal::config()->group( configSectionName() );
+
+    config.writeEntry( "enableProvider", m_enableProvider );
+    config.writeEntry( "synchronise", m_synchronise );
+
+    if ( !m_wallet && config.readEntry( "ignoreWallet", QString() ) != "yes" )
+        askAboutMissingKWallet();
+
+    if( m_wallet )
+    {
+        m_wallet->setFolder( "Amarok" );
+
+        if( m_wallet->writePassword( "gpodder_password", m_password ) > 0 )
+            debug() << "Failed to save gpodder.net pw to kwallet!";
+
+        if( m_wallet->writeEntry( "gpodder_username", m_username.toUtf8() ) > 0 )
+            debug() << "Failed to save gpodder.net username to kwallet!";
+    }
+    else if( config.readEntry( "ignoreWallet", QString() ) == "yes" )
+    {
+        config.writeEntry( "username", m_username );
+        config.writeEntry( "password", m_password );
+    }
+    else
+    {
+        debug() << "Could not access the wallet to save the gpodder.net credentials";
+    }
+
+    config.sync();
+}
+
+
+void
+GpodderServiceConfig::askAboutMissingKWallet()
+{
+    if ( !m_askDiag )
+    {
+        m_askDiag = new KDialog( 0 );
+
+        m_askDiag->setCaption( i18n( "gpodder.net credentials" ) );
+        m_askDiag->setMainWidget( new QLabel( i18n( "No running KWallet found. Would you like Amarok to save your gpodder.net credentials in plaintext?" ), m_askDiag ) );
+        m_askDiag->setButtons( KDialog::Yes | KDialog::No );
+        m_askDiag->setModal( true );
+
+        connect( m_askDiag, SIGNAL( yesClicked() ), this, SLOT( textDialogYes() ) );
+        connect( m_askDiag, SIGNAL( noClicked() ), this, SLOT( textDialogNo() ) );
+    }
+
+    m_askDiag->exec();
+}
+
+void
+GpodderServiceConfig::reset()
+{
+    debug() << "Reset config";
+
+    m_username = "";
+    m_password = "";
+    m_enableProvider = false;
+    m_synchronise = false;
+}
+
+
+void
+GpodderServiceConfig::textDialogYes() //SLOT
+{
+    DEBUG_BLOCK
+
+    KConfigGroup config = KGlobal::config()->group( configSectionName() );
+
+    config.writeEntry( "ignoreWallet", "yes" );
+
+    config.sync();
+}
+
+
+void
+GpodderServiceConfig::textDialogNo() //SLOT
+{
+    DEBUG_BLOCK
+
+    KConfigGroup config = KGlobal::config()->group( configSectionName() );
+
+    config.writeEntry( "ignoreWallet", "no" );
+
+    config.sync();
+}
+
+#include "GpodderServiceConfig.moc"
diff --git a/src/services/gpodder/GpodderService.h b/src/services/gpodder/GpodderServiceConfig.h
similarity index 53%
copy from src/services/gpodder/GpodderService.h
copy to src/services/gpodder/GpodderServiceConfig.h
index fac508a..b5ba91f 100644
--- a/src/services/gpodder/GpodderService.h
+++ b/src/services/gpodder/GpodderServiceConfig.h
@@ -1,7 +1,9 @@
 /****************************************************************************************
+ * Copyright (c) 2007 Shane King <kde at dontletsstart.com>                                *
+ * Copyright (c) 2009 Leo Franchi <lfranchi at kde.org>                                    *
  * Copyright (c) 2010 Stefan Derkits <stefan at derkits.at>                                *
  * Copyright (c) 2010 Christian Wagner <christian.wagner86 at gmx.at>                      *
- * Copyright (c) 2010 Felix Winter <ixos01 at gmail.com>                                   *
+ * Copyright (c) 2010 Felix Winter <ixos01 at gmail.com>                                   * 
  *                                                                                      *
  * 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        *
@@ -16,72 +18,58 @@
  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
  ****************************************************************************************/
 
-#ifndef GPODDERSERVICE_H
-#define GPODDERSERVICE_H
+#ifndef GPODDERSERVICECONFIG_H
+#define GPODDERSERVICECONFIG_H
 
-#include "core/support/Amarok.h"
-#include "services/ServiceBase.h"
+#include <QObject>
+#include <QString>
 
-#include <QItemSelectionModel>
-#include <QSortFilterProxyModel>
-
-class GpodderService;
-
-namespace The
-{
-GpodderService* gpodderService();
+namespace KWallet {
+    class Wallet;
 }
 
-class GpodderServiceFactory : public ServiceFactory
-{
-    Q_OBJECT
+class KDialog;
 
-public:
-    GpodderServiceFactory( QObject *parent, const QVariantList &args );
-    virtual ~GpodderServiceFactory() {}
-
-    virtual void init();
-    virtual QString name();
-    virtual KPluginInfo info();
-    virtual KConfigGroup config();
-
-private slots:
-    void slotCreateGpodderService();
-    void slotRemoveGpodderService();
-
-private:
-    ServiceBase* createGpodderService();
-};
-
-class GpodderService : public ServiceBase
+class GpodderServiceConfig : public QObject
 {
     Q_OBJECT
 
 public:
-    GpodderService( GpodderServiceFactory* parent, const QString& name );
-    virtual ~GpodderService();
+    static const char *configSectionName() { return "Service_gpodder"; }
 
-private slots:
+    GpodderServiceConfig();
+    ~GpodderServiceConfig();
 
-    void subscribe();
-    void itemSelected( CollectionTreeItem *selectedItem );
+    void load();
+    void save();
+    void reset();
 
-private:
-    void init();
-    void polish();
+    const QString &username() { return m_username; }
+    void setUsername( const QString &username ) { m_username = username; }
 
-    virtual Collections::Collection * collection()
-    {
-        return 0;
-    }
+    const QString &password() { return m_password; }
+    void setPassword( const QString &password ) { m_password = password; }
 
-    bool m_inited;
+    bool enableProvider() { return m_enableProvider; }
+    void setEnableProvider( bool enableProvider ) { m_enableProvider = enableProvider; }
 
-    QSortFilterProxyModel *m_proxyModel;
+    bool synchronise() { return m_synchronise; }
+    void setSynchronise( bool synchronise ) { m_synchronise = synchronise; }
 
-    QPushButton *m_subscribeButton;
-    QItemSelectionModel *m_selectionModel;
+private slots:
+    void textDialogYes();
+    void textDialogNo();
 
+private:
+    void askAboutMissingKWallet();
+
+    QString m_username;
+    QString m_password;
+    bool m_enableProvider; //enables PodcastProvider if correct LoginData given
+    bool m_synchronise;
+    
+    KDialog *m_askDiag;
+    KWallet::Wallet *m_wallet;
 };
 
-#endif  // GPODDERSERVICE_H
+#endif // GPODDERSERVICECONFIG_H
diff --git a/src/services/gpodder/GpodderServiceModel.cpp b/src/services/gpodder/GpodderServiceModel.cpp
index d899456..8989426 100644
--- a/src/services/gpodder/GpodderServiceModel.cpp
+++ b/src/services/gpodder/GpodderServiceModel.cpp
@@ -17,22 +17,35 @@
  ****************************************************************************************/
 
 #include "GpodderServiceModel.h"
+#include "GpodderServiceSettings.h"
 #include "GpodderTagTreeItem.h"
 #include "GpodderPodcastTreeItem.h"
-#include <QList>
-#include <QEventLoop>
-
 #include "core/support/Debug.h"
 #include "GpodderPodcastRequestHandler.h"
 
+#include <QList>
+#include <QEventLoop>
+#include <QTimer>
+
 #define INITIAL_TOPTAGS_LOADED 100
 static const int s_numberItemsToLoad = 100;
 
 using namespace mygpo;
 
-GpodderServiceModel::GpodderServiceModel( QObject *parent ) : QAbstractItemModel( parent ), m_request( The::networkAccessManager() )
+GpodderServiceModel::GpodderServiceModel( QObject *parent )
+    : QAbstractItemModel( parent )
+    , m_request( The::networkAccessManager() )
 {
-    rootItem = new GpodderTreeItem();
+    rootItem = new GpodderTreeItem( );
+
+    topTagsItem = new GpodderTreeItem( rootItem, "Top Tags" );
+    rootItem->appendChild( topTagsItem );
+
+    topPodcastsItem = new GpodderTreeItem( rootItem, "Top Podcasts" );
+    rootItem->appendChild( topPodcastsItem );
+
+    suggestedPodcastsItem = new GpodderTreeItem( rootItem, "Suggested Podcasts" );
+    rootItem->appendChild( suggestedPodcastsItem );
 }
 
 GpodderServiceModel::~GpodderServiceModel()
@@ -40,10 +53,9 @@ GpodderServiceModel::~GpodderServiceModel()
     delete rootItem;
 }
 
-QModelIndex GpodderServiceModel::index( int row, int column, const QModelIndex &parent ) const
+QModelIndex
+GpodderServiceModel::index( int row, int column, const QModelIndex &parent ) const
 {
-    DEBUG_BLOCK
-
     if( !hasIndex( row, column, parent ) )
         return QModelIndex();
 
@@ -52,7 +64,7 @@ QModelIndex GpodderServiceModel::index( int row, int column, const QModelIndex &
     if( !parent.isValid() )
         parentItem = rootItem;
     else
-        parentItem = static_cast<GpodderTreeItem*>( parent.internalPointer() );
+        parentItem = static_cast<GpodderTreeItem *>( parent.internalPointer() );
 
     if( parentItem == 0 )
         return QModelIndex();
@@ -64,14 +76,13 @@ QModelIndex GpodderServiceModel::index( int row, int column, const QModelIndex &
         return QModelIndex();
 }
 
-QModelIndex GpodderServiceModel::parent( const QModelIndex &index ) const
+QModelIndex
+GpodderServiceModel::parent( const QModelIndex &index ) const
 {
-    DEBUG_BLOCK
-
     if( !index.isValid() )
         return QModelIndex();
 
-    GpodderTreeItem *childItem = static_cast<GpodderTreeItem*>( index.internalPointer() );
+    GpodderTreeItem *childItem = static_cast<GpodderTreeItem *>( index.internalPointer() );
 
     if( childItem == 0 || childItem->isRoot() )
         return QModelIndex();
@@ -79,9 +90,7 @@ QModelIndex GpodderServiceModel::parent( const QModelIndex &index ) const
     GpodderTreeItem *parentItem = childItem->parent();
 
     if( parentItem == 0 )
-    {
         return QModelIndex();
-    }
 
     int childIndex;
     if( parentItem->isRoot() )
@@ -92,10 +101,9 @@ QModelIndex GpodderServiceModel::parent( const QModelIndex &index ) const
     return createIndex( childIndex, 0, parentItem );
 }
 
-int GpodderServiceModel::rowCount( const QModelIndex &parent ) const
+int
+GpodderServiceModel::rowCount( const QModelIndex &parent ) const
 {
-    DEBUG_BLOCK
-
     GpodderTreeItem *parentItem;
 
     if( !parent.isValid() )
@@ -103,7 +111,7 @@ int GpodderServiceModel::rowCount( const QModelIndex &parent ) const
         return rootItem->childCount();
     }
 
-    parentItem = static_cast<GpodderTreeItem*>( parent.internalPointer() );
+    parentItem = static_cast<GpodderTreeItem *>( parent.internalPointer() );
 
     if( parentItem == 0 )
         return 0;
@@ -111,18 +119,16 @@ int GpodderServiceModel::rowCount( const QModelIndex &parent ) const
     return parentItem->childCount();
 }
 
-int GpodderServiceModel::columnCount( const QModelIndex &parent ) const
+int
+GpodderServiceModel::columnCount( const QModelIndex &parent ) const
 {
-    DEBUG_BLOCK
-
     Q_UNUSED( parent )
     return 1;
 }
 
-QVariant GpodderServiceModel::data( const QModelIndex &index, int role ) const
+QVariant
+GpodderServiceModel::data( const QModelIndex &index, int role ) const
 {
-    DEBUG_BLOCK
-
     if( !index.isValid() )
         return QVariant();
 
@@ -138,29 +144,80 @@ QVariant GpodderServiceModel::data( const QModelIndex &index, int role ) const
     return item->displayData();
 }
 
-void GpodderServiceModel::insertTagList()
+void
+GpodderServiceModel::insertTagList()
 {
-    DEBUG_BLOCK
-
     if( rootItem != 0 )
     {
-        beginInsertRows( QModelIndex(), 0, topTags->list().count() - 1 );
-        rootItem->appendTags( topTags );
+        beginInsertRows( createIndex( 0,0, topTagsItem), 0, topTags->list().count() - 1 );
+        topTagsItem->appendTags( topTags );
         endInsertRows();
     }
 }
 
-void GpodderServiceModel::topTagsRequestError( QNetworkReply::NetworkError error )
+void
+GpodderServiceModel::topTagsRequestError( QNetworkReply::NetworkError error )
 {
+    DEBUG_BLOCK
+
     debug() << "Error in TopTags request: " << error;
+
+    QTimer::singleShot( 20 * 1000, this, SLOT( requestTopTags() ) );
 }
 
-void GpodderServiceModel::topTagsParseError()
+void
+GpodderServiceModel::topTagsParseError()
 {
+    DEBUG_BLOCK
+
     debug() << "Error while parsing TopTags";
+
+    QTimer::singleShot( 20 * 1000, this, SLOT( requestTopTags() ) );
 }
 
-void GpodderServiceModel::insertPodcastList( mygpo::PodcastListPtr podcasts, const QModelIndex & parentItem )
+void
+GpodderServiceModel::topPodcastsRequestError( QNetworkReply::NetworkError error )
+{
+    DEBUG_BLOCK
+
+    debug() << "Error in TopPodcasts request: " << error;
+
+    QTimer::singleShot( 20 * 1000, this, SLOT( requestTopPodcasts() ) );
+}
+
+void
+GpodderServiceModel::topPodcastsParseError()
+{
+    DEBUG_BLOCK
+
+    debug() << "Error while parsing TopPodcasts";
+
+    QTimer::singleShot( 20 * 1000, this, SLOT( requestTopPodcasts() ) );
+}
+
+void
+GpodderServiceModel::suggestedPodcastsRequestError( QNetworkReply::NetworkError error )
+{
+    DEBUG_BLOCK
+
+    debug() << "Error in suggestedPodcasts request: " << error;
+
+    QTimer::singleShot( 20 * 1000, this, SLOT( requestSuggestedPodcasts() ) );
+}
+
+void
+GpodderServiceModel::suggestedPodcastsParseError()
+{
+    DEBUG_BLOCK
+
+    debug() << "Error while parsing suggestedPodcasts";
+
+    QTimer::singleShot( 20 * 1000, this, SLOT( requestSuggestedPodcasts() ) );
+}
+
+void
+GpodderServiceModel::insertPodcastList( mygpo::PodcastListPtr podcasts,
+                                        const QModelIndex &parentItem )
 {
     DEBUG_BLOCK
 
@@ -177,10 +234,9 @@ void GpodderServiceModel::insertPodcastList( mygpo::PodcastListPtr podcasts, con
     emit layoutChanged();
 }
 
-bool GpodderServiceModel::hasChildren( const QModelIndex &parent ) const
+bool
+GpodderServiceModel::hasChildren( const QModelIndex &parent ) const
 {
-    DEBUG_BLOCK
-
     if( !parent.isValid() )
         return true;
 
@@ -202,10 +258,9 @@ bool GpodderServiceModel::hasChildren( const QModelIndex &parent ) const
     }
 }
 
-bool GpodderServiceModel::canFetchMore( const QModelIndex &parent ) const
+bool
+GpodderServiceModel::canFetchMore( const QModelIndex &parent ) const
 {
-    DEBUG_BLOCK
-
     // root item
     if( !parent.isValid() )
     {
@@ -228,32 +283,86 @@ bool GpodderServiceModel::canFetchMore( const QModelIndex &parent ) const
     return false;
 }
 
-void GpodderServiceModel::fetchMore( const QModelIndex &parent )
+void
+GpodderServiceModel::fetchMore( const QModelIndex &parent )
 {
-    DEBUG_BLOCK
-
     // root item
     if( !parent.isValid() )
     {
-        topTags = m_request.topTags( s_numberItemsToLoad );
-        rootItem->setHasChildren( true );
-        connect( topTags.data(), SIGNAL( finished() ), this, SLOT( insertTagList() ) );
-        connect( topTags.data(), SIGNAL( requestError( QNetworkReply::NetworkError ) ), SLOT( topTagsRequestError( QNetworkReply::NetworkError ) ) );
-        connect( topTags.data(), SIGNAL( parseError() ), SLOT( topTagsParseError() ) );
+        requestTopTags();
+        requestTopPodcasts();
+        requestSuggestedPodcasts();
     }
 
-    // TagTreeItem
     GpodderTreeItem *treeItem = static_cast<GpodderTreeItem *>( parent.internalPointer() );
 
+    // TagTreeItem
     if( GpodderTagTreeItem *tagTreeItem = qobject_cast<GpodderTagTreeItem*>( treeItem ) )
     {
+        rootItem->setHasChildren( true );
         tagTreeItem->setHasChildren( true );
-        mygpo::PodcastListPtr podcasts = m_request.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() ) );
+
+        mygpo::PodcastListPtr podcasts =
+                m_request.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()
+{
+    rootItem->setHasChildren( true );
+
+    topTags = m_request.topTags( s_numberItemsToLoad );
+    connect( topTags.data(), SIGNAL( finished() ), this, SLOT( insertTagList() ) );
+    connect( topTags.data(), SIGNAL( requestError( QNetworkReply::NetworkError ) ), SLOT( topTagsRequestError( QNetworkReply::NetworkError ) ) );
+    connect( topTags.data(), SIGNAL( parseError() ), SLOT( topTagsParseError() ) );
+}
+
+void
+GpodderServiceModel::requestTopPodcasts()
+{
+    rootItem->setHasChildren( true );
+
+    mygpo::PodcastListPtr topPodcasts = m_request.toplist( s_numberItemsToLoad );
+    GpodderPodcastRequestHandler *podcastRequestHandler1 = new GpodderPodcastRequestHandler( topPodcasts, createIndex( 0,0, topPodcastsItem ), this );
+    connect( topPodcasts.data(), SIGNAL( finished() ), podcastRequestHandler1, SLOT( finished() ) );
+    connect( topPodcasts.data(), SIGNAL( requestError( QNetworkReply::NetworkError ) ), SLOT( topPodcastsRequestError( QNetworkReply::NetworkError ) ) );
+    connect( topPodcasts.data(), SIGNAL( parseError() ), SLOT( topPodcastsParseError() ) );
+}
+
+void
+GpodderServiceModel::requestSuggestedPodcasts()
+{
+    GpodderServiceConfig config;
+
+    if ( config.enableProvider() )
+    {
+        if ( ( !config.username().isEmpty() ) && ( !config.password().isEmpty() ) )
+        {
+            rootItem->setHasChildren( true );
+
+            mygpo::ApiRequest suggestionsRequest( config.username(), config.password(),
+                                                  new QNetworkAccessManager( this ) );
+
+            mygpo::PodcastListPtr topSuggestions =
+                    suggestionsRequest.suggestions( s_numberItemsToLoad );
+            GpodderPodcastRequestHandler *podcastRequestHandler2 = new GpodderPodcastRequestHandler(
+                        topSuggestions, createIndex( 0,0, suggestedPodcastsItem ), this );
+            connect( topSuggestions.data(), SIGNAL(finished()),
+                     podcastRequestHandler2, SLOT(finished()) );
+            connect( topSuggestions.data(), SIGNAL(requestError( QNetworkReply::NetworkError )),
+                     SLOT(suggestedPodcastsRequestError( QNetworkReply::NetworkError )) );
+            connect( topSuggestions.data(), SIGNAL(parseError()),
+                     SLOT(suggestedPodcastsParseError()) );
+        }
+        else
+            QTimer::singleShot( 5000, this, SLOT(requestSuggestedPodcasts()) );
+    }
+}
diff --git a/src/services/gpodder/GpodderServiceModel.h b/src/services/gpodder/GpodderServiceModel.h
index 1fbc90d..9557541 100644
--- a/src/services/gpodder/GpodderServiceModel.h
+++ b/src/services/gpodder/GpodderServiceModel.h
@@ -50,17 +50,29 @@ private slots:
     void topTagsParseError();
     void insertTagList();
 
+    void topPodcastsRequestError( QNetworkReply::NetworkError error );
+    void topPodcastsParseError();
+
+    void suggestedPodcastsRequestError( QNetworkReply::NetworkError error );
+    void suggestedPodcastsParseError();
+
+    void requestTopTags();
+    void requestTopPodcasts();
+    void requestSuggestedPodcasts();
+
 protected:
     virtual bool canFetchMore( const QModelIndex &parent ) const;
     virtual void fetchMore( const QModelIndex &parent );
 
 private:
     GpodderTreeItem *rootItem;
+    GpodderTreeItem *topTagsItem;
+    GpodderTreeItem *topPodcastsItem;
+    GpodderTreeItem *suggestedPodcastsItem;
     // The gpodder.net topTags
     mygpo::TagListPtr topTags;
     // true if the topTagsRequest has finished and all Top Tags are loaded
     mygpo::ApiRequest m_request;
-
 };
 
 #endif /* GPODDERSERVICEMODEL_H_ */
diff --git a/src/services/gpodder/GpodderServiceSettings.cpp b/src/services/gpodder/GpodderServiceSettings.cpp
new file mode 100644
index 0000000..4f94518
--- /dev/null
+++ b/src/services/gpodder/GpodderServiceSettings.cpp
@@ -0,0 +1,233 @@
+/****************************************************************************************
+ * Copyright (c) 2007 Shane King <kde at dontletsstart.com>                                *
+ * Copyright (c) 2010 Stefan Derkits <stefan at derkits.at>                                *
+ * Copyright (c) 2010 Christian Wagner <christian.wagner86 at gmx.at>                      *
+ * Copyright (c) 2010 Felix Winter <ixos01 at gmail.com>                                   *
+ *                                                                                      *
+ * 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 <http://www.gnu.org/licenses/>.                           *
+ ****************************************************************************************/
+
+#define DEBUG_PREFIX "GpodderServiceSettings"
+
+#include <mygpo-qt/ApiRequest.h>
+#include "GpodderServiceSettings.h"
+#include "core/support/Amarok.h"
+#include "core/support/Debug.h"
+#include "core/podcasts/PodcastProvider.h"
+#include "playlistmanager/PlaylistManager.h"
+#include "NetworkAccessManagerProxy.h"
+#include "ui_GpodderConfigWidget.h"
+
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QVBoxLayout>
+#include <QRegExpValidator>
+
+#include <KMessageBox>
+#include <KPluginFactory>
+#include <KLocale>
+
+
+K_PLUGIN_FACTORY( GpodderServiceSettingsFactory, registerPlugin<GpodderServiceSettings>(); )
+K_EXPORT_PLUGIN( GpodderServiceSettingsFactory( "kcm_amarok_gpodder" ) )
+
+GpodderServiceSettings::GpodderServiceSettings( QWidget *parent, const QVariantList &args )
+        : KCModule( GpodderServiceSettingsFactory::componentData(), parent, args ),
+          m_nam( new QNetworkAccessManager( this ) ),
+          m_enableProvider( false ),
+          m_createDevice( 0 )
+{
+    debug() << "Creating gpodder.net config object";
+
+    QVBoxLayout* l = new QVBoxLayout( this );
+    QWidget *w = new QWidget;
+    m_configDialog = new Ui::GpodderConfigWidget;
+    m_configDialog->setupUi( w );
+    l->addWidget( w );
+
+    connect( m_configDialog->kcfg_GpodderUsername, SIGNAL( textChanged( const QString & ) ), this, SLOT( settingsChanged() ) );
+    connect( m_configDialog->kcfg_GpodderPassword, SIGNAL( textChanged( const QString & ) ), this, SLOT( settingsChanged() ) );
+    connect( m_configDialog->testLogin, SIGNAL( clicked() ), this, SLOT( testLogin() ) );
+
+    load();
+}
+
+
+GpodderServiceSettings::~GpodderServiceSettings()
+{
+    delete m_createDevice;
+}
+
+
+void
+GpodderServiceSettings::save()
+{
+    m_config.setUsername( m_configDialog->kcfg_GpodderUsername->text() );
+    m_config.setPassword( m_configDialog->kcfg_GpodderPassword->text() );
+    m_config.setSynchronise( true );
+    m_config.setEnableProvider( m_enableProvider );
+
+    m_config.save();
+    KCModule::save();
+}
+
+void
+GpodderServiceSettings::testLogin()
+{
+    DEBUG_BLOCK
+
+    m_configDialog->testLogin->setEnabled( false );
+    m_configDialog->testLogin->setText( i18n( "Testing..." ) );
+
+    mygpo::ApiRequest api( m_configDialog->kcfg_GpodderUsername->text(),
+                           m_configDialog->kcfg_GpodderPassword->text(), m_nam );
+    m_devices = api.listDevices( m_configDialog->kcfg_GpodderUsername->text() );
+
+    connect( m_devices.data(), SIGNAL(finished()), SLOT(finished()) );
+    connect( m_devices.data(), SIGNAL(requestError( QNetworkReply::NetworkError )),
+             SLOT(onError( QNetworkReply::NetworkError )) );
+    connect( m_devices.data(), SIGNAL(parseError()), SLOT(onParseError()) );
+}
+
+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<mygpo::DevicePtr> ptrList = m_devices->devicesList();
+    mygpo::DevicePtr devPtr;
+
+    foreach( devPtr, ptrList )
+    {
+        if( devPtr->id().compare( "amarok" ) == 0 )
+        {
+            deviceExists = true;
+            break;
+        }
+    }
+    if( !deviceExists )
+    {
+        mygpo::ApiRequest api( m_configDialog->kcfg_GpodderUsername->text(),
+                               m_configDialog->kcfg_GpodderPassword->text(), m_nam );
+
+        m_createDevice = api.renameDevice( m_configDialog->kcfg_GpodderUsername->text(),
+                                           QLatin1String("amarok"),
+                                           QLatin1String( "Device for gpodder.net in Amarok" ),
+                                           mygpo::Device::OTHER );
+
+        connect( m_createDevice, SIGNAL( finished() ), SLOT( deviceCreationFinished()) );
+        connect( m_createDevice, SIGNAL( error( QNetworkReply::NetworkError ) ),
+                                 SLOT( deviceCreationError( QNetworkReply::NetworkError ) ) );
+    }
+    else
+    {
+        debug() << "amarok device was found, everything looks perfect";
+        m_enableProvider = true;
+    }
+}
+
+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() << "AuthenticationFailed";
+
+        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 godder.net service or other Error occured." ),
+                            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->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";
+
+    m_enableProvider = true;
+}
+
+void
+GpodderServiceSettings::deviceCreationError( QNetworkReply::NetworkError code )
+{
+    debug() << "Error creating Amarok Device";
+    debug() << code;
+
+    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_enableProvider = false;
+    m_configDialog->kcfg_GpodderUsername->setText( "" );
+    m_configDialog->kcfg_GpodderPassword->setText( "" );
+}
+
+void
+GpodderServiceSettings::settingsChanged()
+{
+    m_configDialog->testLogin->setText( i18n( "&Test Login" ) );
+    m_configDialog->testLogin->setEnabled( true );
+    m_enableProvider = false;
+
+    emit changed( true );
+}
diff --git a/src/services/gpodder/GpodderService.h b/src/services/gpodder/GpodderServiceSettings.h
similarity index 56%
copy from src/services/gpodder/GpodderService.h
copy to src/services/gpodder/GpodderServiceSettings.h
index fac508a..ae02d20 100644
--- a/src/services/gpodder/GpodderService.h
+++ b/src/services/gpodder/GpodderServiceSettings.h
@@ -1,4 +1,5 @@
 /****************************************************************************************
+ * Copyright (c) 2007 Shane King <kde at dontletsstart.com>                                *
  * Copyright (c) 2010 Stefan Derkits <stefan at derkits.at>                                *
  * Copyright (c) 2010 Christian Wagner <christian.wagner86 at gmx.at>                      *
  * Copyright (c) 2010 Felix Winter <ixos01 at gmail.com>                                   *
@@ -16,72 +17,56 @@
  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
  ****************************************************************************************/
 
-#ifndef GPODDERSERVICE_H
-#define GPODDERSERVICE_H
+#ifndef GPODDERSERVICESETTINGS_H
+#define GPODDERSERVICESETTINGS_H
 
-#include "core/support/Amarok.h"
-#include "services/ServiceBase.h"
+#include <ApiRequest.h>
+#include "GpodderServiceConfig.h"
+#include "core/podcasts/PodcastMeta.h"
 
-#include <QItemSelectionModel>
-#include <QSortFilterProxyModel>
+#include <QNetworkReply>
 
-class GpodderService;
+#include <kcmodule.h>
 
-namespace The
-{
-GpodderService* gpodderService();
-}
-
-class GpodderServiceFactory : public ServiceFactory
-{
-    Q_OBJECT
+namespace Ui { class GpodderConfigWidget; }
 
-public:
-    GpodderServiceFactory( QObject *parent, const QVariantList &args );
-    virtual ~GpodderServiceFactory() {}
-
-    virtual void init();
-    virtual QString name();
-    virtual KPluginInfo info();
-    virtual KConfigGroup config();
-
-private slots:
-    void slotCreateGpodderService();
-    void slotRemoveGpodderService();
+class QListWidgetItem;
 
-private:
-    ServiceBase* createGpodderService();
-};
-
-class GpodderService : public ServiceBase
+class GpodderServiceSettings : public KCModule
 {
     Q_OBJECT
 
 public:
-    GpodderService( GpodderServiceFactory* parent, const QString& name );
-    virtual ~GpodderService();
+    explicit GpodderServiceSettings( QWidget *parent = 0, const QVariantList &args = QVariantList() );
 
-private slots:
+    virtual ~GpodderServiceSettings();
 
-    void subscribe();
-    void itemSelected( CollectionTreeItem *selectedItem );
+    virtual void save();
+    virtual void load();
+    virtual void defaults();
 
-private:
-    void init();
-    void polish();
+private slots:
+    void testLogin();
 
-    virtual Collections::Collection * collection()
-    {
-        return 0;
-    }
+    void finished();
+    void onError( QNetworkReply::NetworkError code );
+    void onParseError( );
 
-    bool m_inited;
+    void deviceCreationFinished();
+    void deviceCreationError( QNetworkReply::NetworkError code );
+    void settingsChanged();
 
-    QSortFilterProxyModel *m_proxyModel;
+private:
+    QNetworkAccessManager *m_nam;
+    Ui::GpodderConfigWidget *m_configDialog;
+    GpodderServiceConfig m_config;
 
-    QPushButton *m_subscribeButton;
-    QItemSelectionModel *m_selectionModel;
+    mygpo::ApiRequest *m_apiRequest;
+    mygpo::DeviceListPtr m_devices;
+    mygpo::AddRemoveResultPtr m_result;
+    bool m_enableProvider;
+    QNetworkReply *m_createDevice;
 
 };
 
-#endif  // GPODDERSERVICE_H
+#endif // GPODDERSERVICESETTINGS_H
diff --git a/src/services/gpodder/GpodderTreeItem.cpp b/src/services/gpodder/GpodderTreeItem.cpp
index bfa216e..a772e4a 100644
--- a/src/services/gpodder/GpodderTreeItem.cpp
+++ b/src/services/gpodder/GpodderTreeItem.cpp
@@ -21,9 +21,12 @@
 #include "GpodderPodcastTreeItem.h"
 #include "GpodderTagTreeItem.h"
 
-GpodderTreeItem::GpodderTreeItem( GpodderTreeItem *parent ) : QObject( parent ), m_parentItem( parent ), m_hasChildren( false )
+GpodderTreeItem::GpodderTreeItem( GpodderTreeItem *parent, QString name )
+    : QObject( parent )
+    , m_parentItem( parent )
+    , m_name( name )
+    , m_hasChildren( false )
 {
-
 }
 
 GpodderTreeItem::~GpodderTreeItem()
@@ -31,47 +34,56 @@ GpodderTreeItem::~GpodderTreeItem()
     qDeleteAll( m_childItems );
 }
 
-void GpodderTreeItem::appendChild( GpodderTreeItem *item )
+void
+GpodderTreeItem::appendChild( GpodderTreeItem *item )
 {
     m_childItems.append( item );
 }
 
-GpodderTreeItem *GpodderTreeItem::child( int row )
+GpodderTreeItem *
+GpodderTreeItem::child( int row )
 {
     return m_childItems.value( row );
 }
 
-bool GpodderTreeItem::hasChildren() const
+bool
+GpodderTreeItem::hasChildren() const
 {
     return m_hasChildren;
 }
 
-void GpodderTreeItem::setHasChildren( bool hasChildren )
+void
+GpodderTreeItem::setHasChildren( bool hasChildren )
 {
     m_hasChildren = hasChildren;
 }
 
-int GpodderTreeItem::childCount() const
+int
+GpodderTreeItem::childCount() const
 {
     return m_childItems.count();
 }
 
-GpodderTreeItem *GpodderTreeItem::parent() const
+GpodderTreeItem *
+GpodderTreeItem::parent() const
 {
     return m_parentItem;
 }
 
-QVariant GpodderTreeItem::displayData() const
+QVariant
+GpodderTreeItem::displayData() const
 {
-    return QVariant();
+    return m_name;
 }
 
-bool GpodderTreeItem::isRoot() const
+bool
+GpodderTreeItem::isRoot() const
 {
     return ( m_parentItem == 0 );
 }
 
-void GpodderTreeItem::appendTags( mygpo::TagListPtr tags )
+void
+GpodderTreeItem::appendTags( mygpo::TagListPtr tags )
 {
     foreach( mygpo::TagPtr tag, tags->list() )
     {
@@ -80,7 +92,8 @@ void GpodderTreeItem::appendTags( mygpo::TagListPtr tags )
     }
 }
 
-void GpodderTreeItem::appendPodcasts( mygpo::PodcastListPtr podcasts )
+void
+GpodderTreeItem::appendPodcasts( mygpo::PodcastListPtr podcasts )
 {
     foreach( mygpo::PodcastPtr podcast, podcasts->list() )
     {
diff --git a/src/services/gpodder/GpodderTreeItem.h b/src/services/gpodder/GpodderTreeItem.h
index 5698d5b..4e29362 100644
--- a/src/services/gpodder/GpodderTreeItem.h
+++ b/src/services/gpodder/GpodderTreeItem.h
@@ -32,7 +32,7 @@ class GpodderTreeItem : public QObject
 {
     Q_OBJECT
 public:
-    GpodderTreeItem( GpodderTreeItem *parent = 0 );
+    GpodderTreeItem( GpodderTreeItem *parent = 0, QString name = "" );
     virtual ~GpodderTreeItem();
 
     void appendChild( GpodderTreeItem *child );
@@ -48,11 +48,12 @@ public:
 
     virtual QVariant displayData() const;
 
-    virtual void appendTags( mygpo::TagListPtr tags/*, mygpo::ApiRequest& request, GpodderServiceModel *model*/ );
+    virtual void appendTags( mygpo::TagListPtr tags );
     virtual void appendPodcasts( mygpo::PodcastListPtr podcasts );
 private:
-    QList<GpodderTreeItem*> m_childItems;
+    QList<GpodderTreeItem *> m_childItems;
     GpodderTreeItem *m_parentItem;
+    QString m_name;
     bool m_hasChildren;
 };
 
diff --git a/src/services/gpodder/amarok_service_gpodder_config.desktop b/src/services/gpodder/amarok_service_gpodder_config.desktop
new file mode 100644
index 0000000..a69a05c
--- /dev/null
+++ b/src/services/gpodder/amarok_service_gpodder_config.desktop
@@ -0,0 +1,12 @@
+[Desktop Entry]
+Icon=view-services-gpodder-amarok
+Type=Service
+ServiceTypes=Amarok/Plugin
+X-KDE-ServiceTypes=KCModule
+
+X-KDE-Library=kcm_amarok_service_gpodder
+X-KDE-ParentApp=amarok_service_gpodder
+X-KDE-ParentComponents=amarok_service_gpodder
+
+Name=gpodder.net Service Config
+Comment=Configure gpodder.net Credentials


More information about the kde-doc-english mailing list