Version 0.1.3:
authorYves <ymarcoz@n900-sdk.(none)>
Fri, 8 Jan 2010 08:31:12 +0000 (00:31 -0800)
committerYves <ymarcoz@n900-sdk.(none)>
Fri, 8 Jan 2010 08:31:12 +0000 (00:31 -0800)
  * Added dbus service to add new feed (#4992)
  * Fixed #4979, delete feed issue
  * Fixed "Add Feed" dialog
  * Added progress bar and multi-threading for downloading feeds
  * Automatic loading of images in HTML feeds
  * Use dbus to open up external browser

Makefile
data/26x26/feedingit.png [new file with mode: 0644]
data/40x40/feedingit.png [new file with mode: 0644]
debian/changelog
debian/control
src/FeedingIt.py
src/feedingit.service [new file with mode: 0644]
src/feedingitdbus.py [new file with mode: 0644]
src/portrait.py [new file with mode: 0644]
src/rss.py

index 195cd97..ec3788c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,9 +12,12 @@ install:
        install src/feedparser.py ${DESTDIR}/opt/FeedingIt
        install src/portrait.py ${DESTDIR}/opt/FeedingIt
        install src/rss.py ${DESTDIR}/opt/FeedingIt
+       install src/feedingitdbus.py ${DESTDIR}/opt/FeedingIt
        install -d ${DESTDIR}/usr/share/applications/hildon
        install src/FeedingIt.desktop ${DESTDIR}/usr/share/applications/hildon
        install -d ${DESTDIR}/usr/share/icons/hicolor/40x40/apps/
        install data/40x40/feedingit.png ${DESTDIR}/usr/share/icons/hicolor/40x40/apps/
        install -d ${DESTDIR}/usr/share/icons/hicolor/26x26/apps/
-       install data/26x26/feedingit.png ${DESTDIR}/usr/share/icons/hicolor/26x26/apps/
\ No newline at end of file
+       install data/26x26/feedingit.png ${DESTDIR}/usr/share/icons/hicolor/26x26/apps/
+       install -d ${DESTDIR}/usr/share/dbus-1/services/
+       install src/feedingit.service ${DESTDIR}/usr/share/dbus-1/services/
\ No newline at end of file
diff --git a/data/26x26/feedingit.png b/data/26x26/feedingit.png
new file mode 100644 (file)
index 0000000..7c48681
Binary files /dev/null and b/data/26x26/feedingit.png differ
diff --git a/data/40x40/feedingit.png b/data/40x40/feedingit.png
new file mode 100644 (file)
index 0000000..09ef5e4
Binary files /dev/null and b/data/40x40/feedingit.png differ
index e36aaff..f664aa4 100644 (file)
@@ -1,3 +1,14 @@
+feedingit (0.1.3-1) unstable; urgency=low
+
+  * Added dbus service to add new feed (#4992)
+  * Fixed #4979, delete feed issue
+  * Fixed "Add Feed" dialog
+  * Added progress bar and multi-threading for downloading feeds
+  * Automatic loading of images in HTML feeds
+  * Use dbus to open up external browser
+  
+ -- Yves <yves@marcoz.org>  Thu, 07 Jan 2010 23:09:19 -0800
+
 feedingit (0.1.2-1) unstable; urgency=low
 
   * Added read/unread support
index e9db4cb..3666136 100644 (file)
@@ -7,9 +7,9 @@ Standards-Version: 3.7.2
 
 Package: feedingit
 Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, python-gtkhtml2, python, python-hildon
+Depends: ${shlibs:Depends}, ${misc:Depends}, python-gtkhtml2, python, python-hildon, libgtkhtml2-0, python-dbus
 Description: Simple RSS Reader
- Simple RSS Reader, based on feedparser.py
+ Simple RSS Reader, with portrait mode support
 XB-Maemo-Icon-26:
  iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAAXNSR0IArs4c
  6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0
index 184ef4e..4d53933 100644 (file)
@@ -19,8 +19,8 @@
 # ============================================================================
 # Name        : FeedingIt.py
 # Author      : Yves Marcoz
-# Version     : 0.1.1
-# Description : PyGtk Example 
+# Version     : 0.1.3
+# Description : Simple RSS Reader
 # ============================================================================
 
 import gtk
@@ -29,7 +29,7 @@ import pango
 import hildon
 import gtkhtml2
 import time
-import webbrowser
+import dbus
 import pickle
 from os.path import isfile, isdir
 from os import mkdir
@@ -37,26 +37,36 @@ import sys
 import urllib2
 import gobject
 from portrait import FremantleRotation
+import threading
+from feedingitdbus import ServerObject
 
 from rss import *
    
 class AddWidgetWizard(hildon.WizardDialog):
     
-    def __init__(self, parent):
+    def __init__(self, parent, urlIn):
         # Create a Notebook
         self.notebook = gtk.Notebook()
 
         self.nameEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
         self.nameEntry.set_placeholder("Enter Feed Name")
+        vbox = gtk.VBox(False,10)
+        label = gtk.Label("Enter Feed Name:")
+        vbox.pack_start(label)
+        vbox.pack_start(self.nameEntry)
+        self.notebook.append_page(vbox, None)
         
         self.urlEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
-        
         self.urlEntry.set_placeholder("Enter a URL")
-            
+        self.urlEntry.set_text(urlIn)
+        vbox = gtk.VBox(False,10)
+        label = gtk.Label("Enter Feed Name:")
+        vbox.pack_start(label)
+        vbox.pack_start(self.urlEntry)
+        self.notebook.append_page(vbox, None)
+
         labelEnd = gtk.Label("Success")
         
-        self.notebook.append_page(self.nameEntry, None)
-        self.notebook.append_page(self.urlEntry, None) 
         self.notebook.append_page(labelEnd, None)      
 
         hildon.WizardDialog.__init__(self, parent, "Add Feed", self.notebook)
@@ -78,17 +88,73 @@ class AddWidgetWizard(hildon.WizardDialog):
     def some_page_func(self, nb, current, userdata):
         # Validate data for 1st page
         if current == 0:
-            entry = nb.get_nth_page(current)
-            # Check the name is not null
-            return len(entry.get_text()) != 0
+            return len(self.nameEntry.get_text()) != 0
         elif current == 1:
-            entry = nb.get_nth_page(current)
             # Check the url is not null, and starts with http
-            return ( (len(entry.get_text()) != 0) and (entry.get_text().lower().startswith("http")) )
+            return ( (len(self.urlEntry.get_text()) != 0) and (self.urlEntry.get_text().lower().startswith("http")) )
         elif current != 2:
             return False
         else:
             return True
+        
+        
+class Download(threading.Thread):
+    def __init__(self, listing, key):
+        threading.Thread.__init__(self)
+        self.listing = listing
+        self.key = key
+        
+    def run ( self ):
+        self.listing.updateFeed(self.key)
+
+        
+class DownloadDialog():
+    def __init__(self, parent, listing, listOfKeys):
+        self.listOfKeys = listOfKeys
+        self.listing = listing
+        self.total = len(self.listOfKeys)
+        self.current = 0            
+        
+        if self.total>0:
+            self.progress = gtk.ProgressBar()
+            self.waitingWindow = hildon.Note("cancel", parent, "Downloading",
+                                 progressbar=self.progress)
+            self.progress.set_text("Downloading")
+            self.fraction = 0
+            self.progress.set_fraction(self.fraction)
+            # Create a timeout
+            self.timeout_handler_id = gobject.timeout_add(50, self.update_progress_bar)
+            self.waitingWindow.show_all()
+            response = self.waitingWindow.run()
+            self.listOfKeys = []
+            while threading.activeCount() > 1:
+                # Wait for current downloads to finish
+                time.sleep(0.5)
+            self.waitingWindow.destroy()
+        
+    def update_progress_bar(self):
+        #self.progress_bar.pulse()
+        if threading.activeCount() < 4:
+            x = threading.activeCount() - 1
+            k = len(self.listOfKeys)
+            fin = self.total - k - x
+            fraction = float(fin)/float(self.total) + float(x)/(self.total*2.)
+            #print x, k, fin, fraction
+            self.progress.set_fraction(fraction)
+            
+            if len(self.listOfKeys)>0:
+                self.current = self.current+1
+                key = self.listOfKeys.pop()
+                download = Download(self.listing, key)
+                download.start()
+                return True
+            elif threading.activeCount() > 1:
+                return True
+            else:
+                self.waitingWindow.destroy()
+                return False 
+        return True
+        
 
 class DisplayArticle(hildon.StackableWindow):
     def __init__(self, title, text, index):
@@ -126,18 +192,23 @@ class DisplayArticle(hildon.StackableWindow):
         self.document.connect("link_clicked", self._signal_link_clicked)
         self.document.connect("request-url", self._signal_request_url)
         self.connect("destroy", self.destroyWindow)
+        self.timeout_handler_id = gobject.timeout_add(200, self.reloadArticle)
         
     def destroyWindow(self, *args):
         self.emit("article-closed", self.index)
         self.destroy()
         
-    def reloadArticle(self, widget):
+    def reloadArticle(self, *widget):
         self.document.open_stream("text/html")
         self.document.write_stream(self.text)
         self.document.close_stream()
 
     def _signal_link_clicked(self, object, link):
-        webbrowser.open(link)
+        bus = dbus.SystemBus()
+        proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
+        iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
+        #webbrowser.open(link)
+        iface.open_new_window(link)
 
     def _signal_request_url(self, object, url, stream):
         f = urllib2.urlopen(url)
@@ -146,8 +217,9 @@ class DisplayArticle(hildon.StackableWindow):
 
 
 class DisplayFeed(hildon.StackableWindow):
-    def __init__(self, feed, title, key):
+    def __init__(self, listing, feed, title, key):
         hildon.StackableWindow.__init__(self)
+        self.listing = listing
         self.feed = feed
         self.feedTitle = title
         self.set_title(title)
@@ -213,9 +285,10 @@ class DisplayFeed(hildon.StackableWindow):
         self.buttons[index].show()
 
     def button_update_clicked(self, button):
-        self.listing.getFeed(key).updateFeed()
+        disp = DownloadDialog(self, self.listing, [self.key,] )       
+        #self.feed.updateFeed()
         self.clear()
-        self.displayFeed(key)
+        self.displayFeed()
         
     def buttonReadAllClicked(self, button):
         for index in range(self.feed.getNumberOfEntries()):
@@ -257,8 +330,8 @@ class FeedingIt:
 
         self.displayListing() 
         
-    def button_add_clicked(self, button):
-        wizard = AddWidgetWizard(self.window)
+    def button_add_clicked(self, button, urlIn="http://"):
+        wizard = AddWidgetWizard(self.window, urlIn)
         ret = wizard.run()
         if ret == 2:
             (title, url) = wizard.getData()
@@ -268,14 +341,8 @@ class FeedingIt:
         self.displayListing()
         
     def button_update_clicked(self, button, key):
-        #hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
-        if key == "All":
-            self.listing.updateFeeds()
-        else:
-            self.listing.getFeed(key).updateFeed()
-            self.displayFeed(key)            
+        disp = DownloadDialog(self.window, self.listing, self.listing.getListOfFeeds() )           
         self.displayListing()
-        #hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
 
     def button_delete_clicked(self, button):
         self.pickerDialog = hildon.PickerDialog(self.window)
@@ -304,6 +371,7 @@ class FeedingIt:
         self.pickerDialog.destroy()
         if self.show_confirmation_note(self.window, current_selection):
             self.listing.removeFeed(self.mapping[current_selection])
+            
         del self.mapping
         self.displayListing()
 
@@ -333,7 +401,7 @@ class FeedingIt:
             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
                               hildon.BUTTON_ARRANGEMENT_VERTICAL)
             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
-                            + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items / " + str(self.listing.getFeed(key).getNumberOfEntries()))
+                            + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
             button.set_alignment(0,0,1,1)
             button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
             self.vboxListing.pack_start(button, expand=False)
@@ -342,7 +410,7 @@ class FeedingIt:
         self.window.show_all()
     
     def buttonFeedClicked(widget, button, self, window, key):
-        disp = DisplayFeed(self.listing.getFeed(key), self.listing.getFeedTitle(key), key)
+        disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key)
         disp.connect("feed-closed", self.onFeedClosed)
         
     def onFeedClosed(self, object, key):
@@ -358,7 +426,7 @@ class FeedingIt:
 if __name__ == "__main__":
     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
     gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
-    
+    gobject.threads_init()
     if not isdir(CONFIGDIR):
         try:
             mkdir(CONFIGDIR)
@@ -366,4 +434,5 @@ if __name__ == "__main__":
             print "Error: Can't create configuration directory"
             sys.exit(1)
     app = FeedingIt()
+    dbusHandler = ServerObject(app)
     app.run()
diff --git a/src/feedingit.service b/src/feedingit.service
new file mode 100644 (file)
index 0000000..86c7613
--- /dev/null
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.maemo.feedingit
+Exec=/usr/bin/FeedingIt
\ No newline at end of file
diff --git a/src/feedingitdbus.py b/src/feedingitdbus.py
new file mode 100644 (file)
index 0000000..09b305b
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/env python2.5
+
+# 
+# Copyright (c) 2007-2008 INdT.
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 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 Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# ============================================================================
+# Name        : FeedingIt.py
+# Author      : Yves Marcoz
+# Version     : 0.1.3
+# Description : Simple RSS Reader
+# ============================================================================
+
+import dbus
+import dbus.service
+
+class ServerObject(dbus.service.Object):
+    def __init__(self, app):
+        # Here the service name
+        bus_name = dbus.service.BusName('org.maemo.feedingit',bus=dbus.SessionBus())
+        # Here the object path
+        dbus.service.Object.__init__(self, bus_name, '/org/maemo/feedingit')
+        self.app = app
+
+    # Here the interface name, and the method is named same as on dbus.
+    @dbus.service.method('org.maemo.feedingit')
+    def AddFeed(self, url):
+        self.app.button_add_clicked(None, url)
+        return "Done"
diff --git a/src/portrait.py b/src/portrait.py
new file mode 100644 (file)
index 0000000..6d43b0e
--- /dev/null
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+#
+# gPodder - A media aggregator and podcast client
+# Copyright (c) 2005-2009 Thomas Perl and the gPodder Team
+#
+# gPodder is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# gPodder 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/>.
+#
+
+import dbus
+import dbus.glib
+
+import hildon
+import osso
+
+# Replace this with your own gettext() functionality
+#import gpodder
+#_ = gpodder.gettext
+
+
+class FremantleRotation(object):
+    """thp's screen rotation for Maemo 5
+
+    Simply instantiate an object of this class and let it auto-rotate
+    your StackableWindows depending on the device orientation.
+
+    If you need to relayout a window, connect to its "configure-event"
+    signal and measure the ratio of width/height and relayout for that.
+
+    You can set the mode for rotation to AUTOMATIC (default), NEVER or
+    ALWAYS with the set_mode() method.
+    """
+    AUTOMATIC, NEVER, ALWAYS = range(3)
+
+    # Human-readable captions for the above constants
+    #MODE_CAPTIONS = (_('Automatic'), _('Landscape'), _('Portrait'))
+
+    # Privately-used constants
+    _PORTRAIT, _LANDSCAPE = ('portrait', 'landscape')
+    _ENABLE_ACCEL = 'req_accelerometer_enable'
+    _DISABLE_ACCEL = 'req_accelerometer_disable'
+
+    # Defined in mce/dbus-names.h
+    _MCE_SERVICE = 'com.nokia.mce'
+    _MCE_REQUEST_PATH = '/com/nokia/mce/request'
+    _MCE_REQUEST_IF = 'com.nokia.mce.request'
+
+    def __init__(self, app_name, main_window=None, version='1.0', mode=0):
+        """Create a new rotation manager
+
+        app_name    ... The name of your application (for osso.Context)
+        main_window ... The root window (optional, hildon.StackableWindow)
+        version     ... The version of your application (optional, string)
+        mode        ... Initial mode for this manager (default: AUTOMATIC)
+        """
+        self._orientation = None
+        self._main_window = main_window
+        self._stack = hildon.WindowStack.get_default()
+        self._mode = -1
+        self._last_dbus_orientation = None
+        app_id = '-'.join((app_name, self.__class__.__name__))
+        self._osso_context = osso.Context(app_id, version, False)
+        program = hildon.Program.get_instance()
+        program.connect('notify::is-topmost', self._on_topmost_changed)
+        system_bus = dbus.Bus.get_system()
+        system_bus.add_signal_receiver(self._on_orientation_signal, \
+                signal_name='sig_device_orientation_ind', \
+                dbus_interface='com.nokia.mce.signal', \
+                path='/com/nokia/mce/signal')
+        self.set_mode(mode)
+
+    def get_mode(self):
+        """Get the currently-set rotation mode
+
+        This will return one of three values: AUTOMATIC, ALWAYS or NEVER.
+        """
+        return self._mode
+
+    def set_mode(self, new_mode):
+        """Set the rotation mode
+
+        You can set the rotation mode to AUTOMATIC (use hardware rotation
+        info), ALWAYS (force portrait) and NEVER (force landscape).
+        """
+        if new_mode not in (self.AUTOMATIC, self.ALWAYS, self.NEVER):
+            raise ValueError('Unknown rotation mode')
+
+        if self._mode != new_mode:
+            if self._mode == self.AUTOMATIC:
+                # Remember the current "automatic" orientation for later
+                self._last_dbus_orientation = self._orientation
+                # Tell MCE that we don't need the accelerometer anymore
+                self._send_mce_request(self._DISABLE_ACCEL)
+
+            if new_mode == self.NEVER:
+                self._orientation_changed(self._LANDSCAPE)
+            elif new_mode == self.ALWAYS:
+                self._orientation_changed(self._PORTRAIT)
+            elif new_mode == self.AUTOMATIC:
+                # Restore the last-known "automatic" orientation
+                self._orientation_changed(self._last_dbus_orientation)
+                # Tell MCE that we need the accelerometer again
+                self._send_mce_request(self._ENABLE_ACCEL)
+
+            self._mode = new_mode
+
+    def _send_mce_request(self, request):
+        rpc = osso.Rpc(self._osso_context)
+        rpc.rpc_run(self._MCE_SERVICE, \
+                    self._MCE_REQUEST_PATH, \
+                    self._MCE_REQUEST_IF, \
+                    request, \
+                    use_system_bus=True)
+
+    def _on_topmost_changed(self, program, property_spec):
+        # XXX: This seems to never get called on Fremantle(?)
+        if self._mode == self.AUTOMATIC:
+            if program.get_is_topmost():
+                self._send_mce_request(self._ENABLE_ACCEL)
+            else:
+                self._send_mce_request(self._DISABLE_ACCEL)
+
+    def _get_main_window(self):
+        if self._main_window:
+            # If we have gotten the main window as parameter, return it and
+            # don't try "harder" to find another window using the stack
+            return self._main_window
+        else:
+            # The main window is at the "bottom" of the window stack, and as
+            # the list we get with get_windows() is sorted "topmost first", we
+            # simply take the last item of the list to get our main window
+            windows = self._stack.get_windows()
+            if windows:
+                return windows[-1]
+            else:
+                return None
+
+    def _orientation_changed(self, orientation):
+        if self._orientation == orientation:
+            # Ignore repeated requests
+            return
+
+        flags = hildon.PORTRAIT_MODE_SUPPORT
+        if orientation == self._PORTRAIT:
+            flags |= hildon.PORTRAIT_MODE_REQUEST
+
+        window = self._get_main_window()
+        if window is not None:
+            hildon.hildon_gtk_window_set_portrait_flags(window, flags)
+
+        self._orientation = orientation
+
+    def _on_orientation_signal(self, orientation, stand, face, x, y, z):
+        if orientation in (self._PORTRAIT, self._LANDSCAPE):
+            if self._mode == self.AUTOMATIC:
+                # Automatically set the rotation based on hardware orientation
+                self._orientation_changed(orientation)
+            else:
+                # Ignore orientation changes for non-automatic modes, but save
+                # the current orientation for "automatic" mode later on
+                self._last_dbus_orientation = orientation
+
index e094678..09f5273 100644 (file)
@@ -19,8 +19,8 @@
 # ============================================================================
 # Name        : FeedingIt.py
 # Author      : Yves Marcoz
-# Version     : 0.1
-# Description : PyGtk Example 
+# Version     : 0.1.3
+# Description : Simple RSS Reader
 # ============================================================================
 
 from os.path import isfile
@@ -62,26 +62,30 @@ class Feed:
            self.countUnread = 0
            # Initialize the new articles to unread
            for index in range(self.getNumberOfEntries()):
-               if not self.tmpReadItems.has_key(self.getTitle(index)):
-                   self.readItems[self.getTitle(index)] = False
+               if not self.tmpReadItems.has_key(self.getUniqueId(index)):
+                   self.readItems[self.getUniqueId(index)] = False
                else:
-                   self.readItems[self.getTitle(index)] = self.tmpReadItems[self.getTitle(index)]
-               if self.readItems[self.getTitle(index)]==False:
+                   self.readItems[self.getUniqueId(index)] = self.tmpReadItems[self.getUniqueId(index)]
+               if self.readItems[self.getUniqueId(index)]==False:
                   self.countUnread = self.countUnread + 1
            del tmp
            self.saveFeed()
     
     def setEntryRead(self, index):
-        if self.readItems[self.getTitle(index)]==False:
+        if self.readItems[self.getUniqueId(index)]==False:
             self.countUnread = self.countUnread - 1
-            self.readItems[self.getTitle(index)] = True
+            self.readItems[self.getUniqueId(index)] = True
     
     def isEntryRead(self, index):
-        return self.readItems[self.getTitle(index)]
+        return self.readItems[self.getUniqueId(index)]
     
     def getTitle(self, index):
         return self.entries[index]["title"]
     
+    def getUniqueId(self,index):
+        entry = self.entries[index]
+        return getId(time.strftime("%a, %d %b %Y %H:%M:%S",entry["updated_parsed"]) + entry["title"])
+    
     def getUpdateTime(self):
         return self.updateTime
     
@@ -103,17 +107,21 @@ class Feed:
         except:
             return []
     
-    def getArticle(self, index):
-        self.setEntryRead(index)
+    def getContent(self, index):
         entry = self.entries[index]
-        title = entry.get('title', 'No title')
-        #content = entry.get('content', entry.get('summary_detail', {}))
         if entry.has_key('content'):
             content = entry.content[0].value
         else:
             content = entry.get('summary', '')
-        #print content.keys()
-        #.get('value', "No Data")
+        return content
+    
+    def getArticle(self, index):
+        self.setEntryRead(index)
+        entry = self.entries[index]
+        title = entry.get('title', 'No title')
+        #content = entry.get('content', entry.get('summary_detail', {}))
+        content = self.getContent(index)
+
         link = entry.get('link', 'NoLink')
         date = time.strftime("%a, %d %b %Y %H:%M:%S",entry["updated_parsed"])
         #text = '''<div style="color: black; background-color: white;">'''
@@ -148,6 +156,9 @@ class Listing:
         for key in self.listOfFeeds.keys():
             self.feeds[key].updateFeed()
             
+    def updateFeed(self, key):
+        self.feeds[key].updateFeed()
+            
     def getFeed(self, key):
         return self.feeds[key]
     
@@ -176,6 +187,7 @@ class Listing:
         del self.feeds[key]
         if isfile(CONFIGDIR+key):
            remove(CONFIGDIR+key)
+        self.saveConfig()
     
     def saveConfig(self):
         file = open(CONFIGDIR+"feeds.pickle", "w")