Added config dialogs + auto-update
authorYves <ymarcoz@n900-sdk.(none)>
Tue, 26 Jan 2010 07:59:29 +0000 (23:59 -0800)
committerYves <ymarcoz@n900-sdk.(none)>
Tue, 26 Jan 2010 07:59:29 +0000 (23:59 -0800)
Makefile
data/26x26/feedingit.png [deleted file]
data/40x40/feedingit.png [deleted file]
debian/changelog
debian/control
src/FeedingIt.py
src/config.py [new file with mode: 0644]
src/opml.py
src/rss.py

index afd916f..65e3107 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -13,12 +13,13 @@ install:
        install src/portrait.py ${DESTDIR}/opt/FeedingIt
        install src/rss.py ${DESTDIR}/opt/FeedingIt
        install src/opml.py ${DESTDIR}/opt/FeedingIt
+       install src/config.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 data/40px.png ${DESTDIR}/usr/share/icons/hicolor/40x40/apps/feedingit.png
        install -d ${DESTDIR}/usr/share/icons/hicolor/26x26/apps/
-       install data/26x26/feedingit.png ${DESTDIR}/usr/share/icons/hicolor/26x26/apps/
+       install data/26px.png ${DESTDIR}/usr/share/icons/hicolor/26x26/apps/feedingit.png
        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
deleted file mode 100644 (file)
index 7c48681..0000000
Binary files a/data/26x26/feedingit.png and /dev/null differ
diff --git a/data/40x40/feedingit.png b/data/40x40/feedingit.png
deleted file mode 100644 (file)
index 09ef5e4..0000000
Binary files a/data/40x40/feedingit.png and /dev/null differ
index 56c5823..097e8f6 100644 (file)
@@ -1,3 +1,9 @@
+feedingit (0.3.1-1) unstable; urgency=low
+
+  * Fixed socket timeouts
+  
+ -- Yves <yves@marcoz.org>  Sat, 23 Jan 2010 13:31:19 -0800
+
 feedingit (0.2.3-2) unstable; urgency=low
 
   * Fixed missing python-osso dependency
index e422bb8..e85119c 100644 (file)
@@ -11,40 +11,49 @@ Package: feedingit
 Architecture: any
 Depends: ${shlibs:Depends}, ${misc:Depends}, python-gtkhtml2, python, python-hildon, libgtkhtml2-0, python-dbus, python-osso
 Description: Simple RSS Reader
- Simple RSS Reader, with portrait mode support, and swipe gestures in articles
+ Simple RSS Reader
+ Its main features are:
+ - Portrait mode support
+ - Auto-update
+ - Swipe left or right while in articles to move to the previous or next article
+ - Import/Export of OPML files
 XB-Maemo-Icon-26:
- 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
\ No newline at end of file
+ iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAAXNSR0IArs4c                                                                                                                                     
+ 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
+ WUsq//fg/wAnQYd0SCjJNQAAAABJRU5ErkJggg
\ No newline at end of file
index 3a57906..439589e 100644 (file)
@@ -40,10 +40,17 @@ from portrait import FremantleRotation
 import threading
 import thread
 from feedingitdbus import ServerObject
+from config import Config
 
 from rss import *
 from opml import GetOpmlData, ExportOpmlData
    
+import socket
+timeout = 5
+socket.setdefaulttimeout(timeout)
+
+CONFIGDIR="/home/user/.feedingit/"
+
 class AddWidgetWizard(hildon.WizardDialog):
     
     def __init__(self, parent, urlIn):
@@ -102,32 +109,61 @@ class AddWidgetWizard(hildon.WizardDialog):
             return True
 
 class GetImage(threading.Thread):
-    def __init__(self, url):
+    def __init__(self, url, stream):
         threading.Thread.__init__(self)
         self.url = url
+        self.stream = stream
     
     def run(self):
         f = urllib2.urlopen(self.url)
-        self.data = f.read()
+        data = f.read()
         f.close()
+        self.stream.write(data)
+        self.stream.close()
 
+class ImageDownloader():
+    def __init__(self):
+        self.images = []
+        self.downloading = False
+        
+    def queueImage(self, url, stream):
+        self.images.append((url, stream))
+        if not self.downloading:
+            self.downloading = True
+            gobject.timeout_add(50, self.checkQueue)
+        
+    def checkQueue(self):
+        for i in range(4-threading.activeCount()):
+            if len(self.images) > 0:
+                (url, stream) = self.images.pop() 
+                GetImage(url, stream).start()
+        if len(self.images)>0:
+            gobject.timeout_add(200, self.checkQueue)
+        else:
+            self.downloading=False
+            
+    def stopAll(self):
+        self.images = []
+            
         
 class Download(threading.Thread):
-    def __init__(self, listing, key):
+    def __init__(self, listing, key, config):
         threading.Thread.__init__(self)
         self.listing = listing
         self.key = key
+        self.config = config
         
     def run (self):
-        self.listing.updateFeed(self.key)
+        self.listing.updateFeed(self.key, self.config.getExpiry())
 
         
 class DownloadDialog():
-    def __init__(self, parent, listing, listOfKeys):
+    def __init__(self, parent, listing, listOfKeys, config):
         self.listOfKeys = listOfKeys[:]
         self.listing = listing
         self.total = len(self.listOfKeys)
-        self.current = 0            
+        self.config = config
+        self.current = 0
         
         if self.total>0:
             self.progress = gtk.ProgressBar()
@@ -143,7 +179,7 @@ class DownloadDialog():
             self.listOfKeys = []
             while threading.activeCount() > 1:
                 # Wait for current downloads to finish
-                time.sleep(0.5)
+                time.sleep(0.1)
             self.waitingWindow.destroy()
         
     def update_progress_bar(self):
@@ -159,7 +195,7 @@ class DownloadDialog():
             if len(self.listOfKeys)>0:
                 self.current = self.current+1
                 key = self.listOfKeys.pop()
-                download = Download(self.listing, key)
+                download = Download(self.listing, key, self.config)
                 download.start()
                 return True
             elif threading.activeCount() > 1:
@@ -279,10 +315,14 @@ class SortList(gtk.Dialog):
                
 
 class DisplayArticle(hildon.StackableWindow):
-    def __init__(self, title, text, index):
+    def __init__(self, title, text, link, index, key, listing):
         hildon.StackableWindow.__init__(self)
+        self.imageDownloader = ImageDownloader()
+        self.listing=listing
+        self.key = key
         self.index = index
         self.text = text
+        self.link = link
         self.set_title(title)
         self.images = []
         
@@ -306,44 +346,60 @@ class DisplayArticle(hildon.StackableWindow):
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
         button.set_label("Allow Horizontal Scrolling")
         button.connect("clicked", self.horiz_scrolling_button)
+        menu.append(button)
         
+        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        button.set_label("Open in Browser")
+        button.connect("clicked", self._signal_link_clicked, self.link)
+        menu.append(button)
+        
+        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        button.set_label("Add to Archived Articles")
+        button.connect("clicked", self.archive_button)
         menu.append(button)
+        
         self.set_app_menu(menu)
         menu.show_all()
         
         self.add(self.pannable_article)
         
-        self.show_all()
+        self.pannable_article.show_all()
 
         self.destroyId = self.connect("destroy", self.destroyWindow)
-        self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
+        #self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
 
     def gesture(self, widget, direction, startx, starty):
         if (direction == 3):
             self.emit("article-next", self.index)
         if (direction == 2):
             self.emit("article-previous", self.index)
-        self.timeout_handler_id = gobject.timeout_add(200, self.destroyWindow)
+        #self.timeout_handler_id = gobject.timeout_add(200, self.destroyWindow)
 
     def destroyWindow(self, *args):
+        self.disconnect(self.destroyId)
         self.emit("article-closed", self.index)
+        self.imageDownloader.stopAll()
         self.destroy()
         
     def horiz_scrolling_button(self, *widget):
         self.pannable_article.disconnect(self.gestureId)
         self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
         
-    def reloadArticle(self, *widget):
-        if threading.activeCount() > 1:
+    def archive_button(self, *widget):
+        # Call the listing.addArchivedArticle
+        self.listing.addArchivedArticle(self.key, self.index)
+        
+    #def reloadArticle(self, *widget):
+    #    if threading.activeCount() > 1:
             # Image thread are still running, come back in a bit
-            return True
-        else:
-            for (stream, imageThread) in self.images:
-                imageThread.join()
-                stream.write(imageThread.data)
-                stream.close()
-            return False
-        self.show_all()
+    #        return True
+    #    else:
+    #        for (stream, imageThread) in self.images:
+    #            imageThread.join()
+    #            stream.write(imageThread.data)
+    #            stream.close()
+    #        return False
+    #    self.show_all()
 
     def _signal_link_clicked(self, object, link):
         bus = dbus.SystemBus()
@@ -352,25 +408,31 @@ class DisplayArticle(hildon.StackableWindow):
         iface.open_new_window(link)
 
     def _signal_request_url(self, object, url, stream):
-        imageThread = GetImage(url)
-        imageThread.start()
-        self.images.append((stream, imageThread))
+        #print url
+        self.imageDownloader.queueImage(url, stream)
+        #imageThread = GetImage(url)
+        #imageThread.start()
+        #self.images.append((stream, imageThread))
 
 
 class DisplayFeed(hildon.StackableWindow):
-    def __init__(self, listing, feed, title, key):
+    def __init__(self, listing, feed, title, key, config):
         hildon.StackableWindow.__init__(self)
         self.listing = listing
         self.feed = feed
         self.feedTitle = title
         self.set_title(title)
         self.key=key
+        self.config = config
+        
+        self.disp = False
         
         menu = hildon.AppMenu()
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
         button.set_label("Update Feed")
         button.connect("clicked", self.button_update_clicked)
         menu.append(button)
+        
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
         button.set_label("Mark All As Read")
         button.connect("clicked", self.buttonReadAllClicked)
@@ -385,7 +447,7 @@ class DisplayFeed(hildon.StackableWindow):
     def destroyWindow(self, *args):
         self.emit("feed-closed", self.key)
         self.destroy()
-        self.feed.saveFeed()
+        self.feed.saveFeed(CONFIGDIR)
 
     def displayFeed(self):
         self.vboxFeed = gtk.VBox(False, 10)
@@ -399,10 +461,10 @@ class DisplayFeed(hildon.StackableWindow):
             label = button.child
             if self.feed.isEntryRead(index):
                 #label.modify_font(pango.FontDescription("sans 16"))
-                label.modify_font(pango.FontDescription(self.listing.getReadFont()))
+                label.modify_font(pango.FontDescription(self.config.getReadFont()))
             else:
                 #print self.listing.getFont() + " bold"
-                label.modify_font(pango.FontDescription(self.listing.getUnreadFont()))
+                label.modify_font(pango.FontDescription(self.config.getUnreadFont()))
                 #label.modify_font(pango.FontDescription("sans bold 23"))
                 #"sans bold 16"
             label.set_line_wrap(True)
@@ -419,33 +481,52 @@ class DisplayFeed(hildon.StackableWindow):
         
     def clear(self):
         self.remove(self.pannableFeed)
-        
-    def button_clicked(self, button, index):
-        self.disp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), index)
+
+    def button_clicked(self, button, index, previous=False):
+        newDisp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), self.feed.getLink(index), index, self.key, self.listing)
         self.ids = []
-        self.ids.append(self.disp.connect("article-closed", self.onArticleClosed))
-        self.ids.append(self.disp.connect("article-next", self.nextArticle))
-        self.ids.append(self.disp.connect("article-previous", self.previousArticle))
+        self.ids.append(newDisp.connect("article-closed", self.onArticleClosed))
+        self.ids.append(newDisp.connect("article-next", self.nextArticle))
+        self.ids.append(newDisp.connect("article-previous", self.previousArticle))
+        stack = hildon.WindowStack.get_default()
+        if previous:
+            tmp = stack.pop(1)
+            stack.push(newDisp)
+            del tmp
+            newDisp.show_all()
+            #stack.push(tmp)
+            #if not self.disp == False:
+            #   self.disp.destroyWindow()
+        else:
+            if not self.disp == False:
+                #stack.pop(1)
+                stack.pop_and_push(1,newDisp)
+            else:
+                stack.push(newDisp)
+            #newDisp.show_all()
+            #if not self.disp == False:
+                #self.disp.destroyWindow()
+        self.disp = newDisp
 
     def nextArticle(self, object, index):
         label = self.buttons[index].child
-        label.modify_font(pango.FontDescription(self.listing.getReadFont()))
+        label.modify_font(pango.FontDescription(self.config.getReadFont()))
         index = (index+1) % self.feed.getNumberOfEntries()
         self.button_clicked(object, index)
 
     def previousArticle(self, object, index):
         label = self.buttons[index].child
-        label.modify_font(pango.FontDescription(self.listing.getReadFont()))
+        label.modify_font(pango.FontDescription(self.config.getReadFont()))
         index = (index-1) % self.feed.getNumberOfEntries()
-        self.button_clicked(object, index)
+        self.button_clicked(object, index, True)
 
     def onArticleClosed(self, object, index):
         label = self.buttons[index].child
-        label.modify_font(pango.FontDescription(self.listing.getReadFont()))
+        label.modify_font(pango.FontDescription(self.config.getReadFont()))
         self.buttons[index].show()
 
     def button_update_clicked(self, button):
-        disp = DownloadDialog(self, self.listing, [self.key,] )       
+        disp = DownloadDialog(self, self.listing, [self.key,], self.config )       
         #self.feed.updateFeed()
         self.clear()
         self.displayFeed()
@@ -454,16 +535,17 @@ class DisplayFeed(hildon.StackableWindow):
         for index in range(self.feed.getNumberOfEntries()):
             self.feed.setEntryRead(index)
             label = self.buttons[index].child
-            label.modify_font(pango.FontDescription(self.listing.getReadFont()))
+            label.modify_font(pango.FontDescription(self.config.getReadFont()))
             self.buttons[index].show()
 
 
 class FeedingIt:
     def __init__(self):
-        self.listing = Listing()
+        self.listing = Listing(CONFIGDIR)
         
         # Init the windows
         self.window = hildon.StackableWindow()
+        self.config = Config(self.window, CONFIGDIR+"config.ini")
         self.window.set_title("FeedingIt")
         FremantleRotation("FeedingIt", main_window=self.window)
         menu = hildon.AppMenu()
@@ -472,6 +554,7 @@ class FeedingIt:
         button.set_label("Update All Feeds")
         button.connect("clicked", self.button_update_clicked, "All")
         menu.append(button)
+        
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
         button.set_label("Add Feed")
         button.connect("clicked", self.button_add_clicked)
@@ -483,8 +566,8 @@ class FeedingIt:
         menu.append(button)
 
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Listing Font Size")
-        button.connect("clicked", self.button_font_clicked)
+        button.set_label("Preferences")
+        button.connect("clicked", self.button_preferences_clicked)
         menu.append(button)
        
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
@@ -503,7 +586,10 @@ class FeedingIt:
         self.feedWindow = hildon.StackableWindow()
         self.articleWindow = hildon.StackableWindow()
 
-        self.displayListing() 
+        self.displayListing()
+        self.autoupdate = False
+        self.checkAutoUpdate()
+
 
     def button_export_clicked(self, button):
         opml = ExportOpmlData(self.window, self.listing)
@@ -533,37 +619,12 @@ class FeedingIt:
         self.displayListing()
         
     def button_update_clicked(self, button, key):
-        disp = DownloadDialog(self.window, self.listing, self.listing.getListOfFeeds() )           
+        disp = DownloadDialog(self.window, self.listing, self.listing.getListOfFeeds(), self.config )           
         self.displayListing()
 
-    def button_font_clicked(self, button):
-        self.pickerDialog = hildon.PickerDialog(self.window)
-        #HildonPickerDialog
-        selector = self.create_selector()
-        self.pickerDialog.set_selector(selector)
-        self.pickerDialog.show_all()
-        
-    def create_selector(self):
-        selector = hildon.TouchSelector(text=True)
-        # Selection multiple
-        #selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE)
-        self.mapping = {}
-
-        current_size = self.listing.getFontSize()
-        index = 0
-        for size in range(12,24):
-            iter = selector.append_text(str(size))
-            if str(size) == current_size: 
-                selector.set_active(0, index)
-            index += 1
-        selector.connect("changed", self.selection_changed)
-        return selector
-
-    def selection_changed(self, widget, data):
-        current_selection = widget.get_current_text()
-        if current_selection:
-            self.listing.setFont(current_selection)
-        self.pickerDialog.destroy()
+    def button_preferences_clicked(self, button):
+        dialog = self.config.createDialog()
+        dialog.connect("destroy", self.checkAutoUpdate)
 
     def show_confirmation_note(self, parent, title):
         note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
@@ -600,19 +661,36 @@ class FeedingIt:
         self.window.show_all()
 
     def buttonFeedClicked(widget, button, self, window, key):
-        disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key)
+        disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key, self.config)
         disp.connect("feed-closed", self.onFeedClosed)
 
     def onFeedClosed(self, object, key):
-        self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
-                            + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
-        self.buttons[key].show()
+        self.displayListing()
+        #self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
+        #                    + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
+        #self.buttons[key].show()
      
     def run(self):
         self.window.connect("destroy", gtk.main_quit)
         gtk.main()
         self.listing.saveConfig()
 
+    def checkAutoUpdate(self, *widget):
+        if self.config.isAutoUpdateEnabled():
+            if not self.autoupdate:
+                self.autoupdateId = gobject.timeout_add(int(self.config.getUpdateInterval()*3600000), self.automaticUpdate)
+                self.autoupdate = True
+        else:
+            if self.autoupdate:
+                gobject.source_remove(self.autoupdateId)
+                self.autoupdate = False
+
+    def automaticUpdate(self, *widget):
+        # Need to check for internet connection
+        # If no internet connection, try again in 10 minutes:
+        # gobject.timeout_add(int(5*3600000), self.automaticUpdate)
+        self.button_update_clicked(None, None)
+        return True
 
 if __name__ == "__main__":
     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
diff --git a/src/config.py b/src/config.py
new file mode 100644 (file)
index 0000000..319a469
--- /dev/null
@@ -0,0 +1,147 @@
+#!/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.2.2
+# Description : Simple RSS Reader
+# ============================================================================
+
+import gtk
+import hildon
+import ConfigParser
+import gobject
+
+section = "FeedingIt"
+ranges = { "updateInterval":[0.02, 0.5, 1, 2, 4, 12, 24], "expiry":[24, 48, 72], "fontSize":range(12,24) }
+titles = {"updateInterval":"Auto-update Interval", "expiry":"Expiry For Articles", "fontSize":"Font Size For Article Listing"}
+subtitles = {"updateInterval":"Update every %s hours", "expiry":"Delete articles after %s hours", "fontSize":"%s pixels"}
+
+class Config():
+    def __init__(self, parent, configFilename):
+        self.configFilename = configFilename
+        self.parent = parent
+        # Load config
+        self.loadConfig()
+        
+    def createDialog(self):
+        
+        self.window = gtk.Dialog("Preferences", self.parent)
+        #self.vbox = gtk.VBox(False, 10)
+        self.buttons = {}
+        for setting in ["fontSize", "expiry", "updateInterval"]:
+            picker = hildon.PickerButton(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
+            selector = self.create_selector(ranges[setting], setting)
+            picker.set_selector(selector)
+            picker.set_title(titles[setting])
+            picker.set_text(titles[setting], subtitles[setting] % self.config[setting])
+            picker.set_name('HildonButton-finger')
+            picker.set_alignment(0,0,1,1)
+            self.buttons[setting] = picker
+            self.window.vbox.pack_start(picker)
+        
+        button = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
+        button.set_label("Auto-update Enabled")
+        button.set_active(self.config["autoupdate"])
+        button.connect("toggled", self.button_toggled)
+        
+        self.window.vbox.pack_start(button)
+        
+        self.window.connect("destroy", self.onExit)
+        #self.window.add(self.vbox)
+        self.window.show_all()
+        return self.window
+
+    def onExit(self, *widget):
+        self.saveConfig()
+        self.window.destroy()
+
+    def button_toggled(self, widget):
+        #print "widget", widget.get_active()
+        if (widget.get_active()):
+            self.config["autoupdate"] = True
+        else:
+            self.config["autoupdate"] = False
+        #print "autoup",  self.autoupdate
+        self.saveConfig()
+        
+    def selection_changed(self, selector, button, setting):
+        current_selection = selector.get_current_text()
+        if current_selection:
+            self.config[setting] = current_selection
+        gobject.idle_add(self.updateButton, setting)
+        self.saveConfig()
+        
+    def updateButton(self, setting):
+        self.buttons[setting].set_text(titles[setting], subtitles[setting] % self.config[setting])
+        
+    def loadConfig(self):
+        self.config = {}
+        try:
+            configParser = ConfigParser.RawConfigParser()
+            configParser.read(self.configFilename)
+            self.config["fontSize"] = configParser.getint(section, "fontSize")
+            self.config["expiry"] = configParser.getint(section, "expiry")
+            self.config["autoupdate"] = configParser.getboolean(section, "autoupdate")
+            self.config["updateInterval"] = configParser.getfloat(section, "updateInterval")
+        except:
+            self.config["fontSize"] = 16
+            self.config["expiry"] = 24
+            self.config["autoupdate"] = False
+            self.config["updateInterval"] = 4
+        
+    def saveConfig(self):
+        configParser = ConfigParser.RawConfigParser()
+        configParser.add_section(section)
+        configParser.set(section, 'fontSize', str(self.config["fontSize"]))
+        configParser.set(section, 'expiry', str(self.config["expiry"]))
+        configParser.set(section, 'autoupdate', str(self.config["autoupdate"]))
+        configParser.set(section, 'updateInterval', str(self.config["updateInterval"]))
+
+        # Writing our configuration file
+        file = open(self.configFilename, 'wb')
+        configParser.write(file)
+        file.close()
+
+    def create_selector(self, choices, setting):
+        #self.pickerDialog = hildon.PickerDialog(self.parent)
+        selector = hildon.TouchSelector(text=True)
+        index = 0
+        for item in choices:
+            iter = selector.append_text(str(item))
+            if str(self.config[setting]) == str(item): 
+                selector.set_active(0, index)
+            index += 1
+        selector.connect("changed", self.selection_changed, setting)
+        #self.pickerDialog.set_selector(selector)
+        return selector
+        #self.pickerDialog.show_all()
+
+    def getFontSize(self):
+        return self.config["fontSize"]
+    def getExpiry(self):
+        return self.config["expiry"]
+    def isAutoUpdateEnabled(self):
+        return self.config["autoupdate"]
+    def getUpdateInterval(self):
+        return float(self.config["updateInterval"])
+    def getReadFont(self):
+        return "sans %s" % self.config["fontSize"]
+    def getUnreadFont(self):
+        return "sans bold %s" % self.config["fontSize"]
\ No newline at end of file
index 8593479..552dcc3 100644 (file)
@@ -93,7 +93,8 @@ class ExportOpmlData():
         for key in listing.getListOfFeeds():
             title = listing.getFeedTitle(key)
             url = listing.getFeedUrl(key)
-            opml_text += """\n\t\t<outline  type="rss" text="%s" title="%s" xmlUrl="%s"/>""" % (title, title, url)
+            if not title == "Archived Articles": 
+                opml_text += """\n\t\t<outline  type="rss" text="%s" title="%s" xmlUrl="%s"/>""" % (title, title, url)
         opml_text += """\n</body>\n</opml>\n"""
         return opml_text
         
index 6d70219..bc63918 100644 (file)
@@ -30,8 +30,9 @@ import pickle
 import md5
 import feedparser
 import time
+import urllib2
 
-CONFIGDIR="/home/user/.feedingit/"
+#CONFIGDIR="/home/user/.feedingit/"
 
 def getId(string):
     return md5.new(string).hexdigest()
@@ -40,44 +41,52 @@ class Feed:
     # Contains all the info about a single feed (articles, ...), and expose the data
     def __init__(self, name, url):
         self.entries = []
-        self.fontSize = "+0"
         self.readItems = {}
         self.countUnread = 0
         self.name = name
         self.url = url
         self.updateTime = "Never"
 
-    def getFontSize(self):
-        try:
-            return self.fontSize
-        except:
-            self.fontSize = "+0"
-            return self.fontSize
-
-    def saveFeed(self):
-        file = open(CONFIGDIR+getId(self.name), "w")
+    def saveFeed(self, configdir):
+        file = open(configdir+getId(self.name), "w")
         pickle.dump(self, file )
         file.close()
 
-    def updateFeed(self):
+    def updateFeed(self, configdir, expiryTime=24):
+        # Expiry time is in hours
         tmp=feedparser.parse(self.url)
         # Check if the parse was succesful (number of entries > 0, else do nothing)
         if len(tmp["entries"])>0:
-           self.tmpReadItems = self.readItems
-           self.readItems = {}
-           self.updateTime = time.asctime()
+           #reversedEntries = self.getEntries()
+           #reversedEntries.reverse()
+           tmpIds = []
+           for entry in tmp["entries"]:
+               tmpIds.append(self.getUniqueId(-1, entry))
+           for entry in self.getEntries():
+               currentTime = time.time()
+               expiry = float(expiryTime) * 3600.
+               if entry.has_key("updated_parsed"):
+                   articleTime = time.mktime(entry["updated_parsed"])
+                   if currentTime - articleTime < expiry:
+                       id = self.getUniqueId(-1, entry)
+                       if not id in tmpIds:
+                           tmp["entries"].append(entry)
+                   
            self.entries = tmp["entries"]
            self.countUnread = 0
            # Initialize the new articles to unread
+           tmpReadItems = self.readItems
+           self.readItems = {}
            for index in range(self.getNumberOfEntries()):
-               if not self.tmpReadItems.has_key(self.getUniqueId(index)):
+               if not tmpReadItems.has_key(self.getUniqueId(index)):
                    self.readItems[self.getUniqueId(index)] = False
                else:
-                   self.readItems[self.getUniqueId(index)] = self.tmpReadItems[self.getUniqueId(index)]
+                   self.readItems[self.getUniqueId(index)] = tmpReadItems[self.getUniqueId(index)]
                if self.readItems[self.getUniqueId(index)]==False:
                   self.countUnread = self.countUnread + 1
            del tmp
-           self.saveFeed()
+           self.updateTime = time.asctime()
+           self.saveFeed(configdir)
     
     def setEntryRead(self, index):
         if self.readItems[self.getUniqueId(index)]==False:
@@ -90,8 +99,18 @@ class Feed:
     def getTitle(self, index):
         return self.entries[index]["title"]
     
-    def getUniqueId(self,index):
-        entry = self.entries[index]
+    def getLink(self, index):
+        return self.entries[index]["link"]
+    
+    def getDate(self, index):
+        try:
+            return self.entries[index]["updated_parsed"]
+        except:
+            return time.localtime()
+    
+    def getUniqueId(self, index, entry=None):
+        if index >=0:
+            entry = self.entries[index]
         if entry.has_key("updated_parsed"):
             return getId(time.strftime("%a, %d %b %Y %H:%M:%S",entry["updated_parsed"]) + entry["title"])
         elif entry.has_key("link"):
@@ -150,19 +169,56 @@ class Feed:
         text += content
         return text    
 
+class ArchivedArticles(Feed):
+    def addArchivedArticle(self, title, link, updated_parsed, configdir):
+        entry = {}
+        entry["title"] = title
+        entry["link"] = link
+        entry["downloaded"] = False
+        entry["summary"] = '<a href=\"' + link + '\">' + title + "</a>"
+        entry["updated_parsed"] = updated_parsed
+        self.entries.append(entry)
+        self.readItems[self.getUniqueId(len(self.entries)-1)] = False
+        self.countUnread = self.countUnread + 1
+        self.saveFeed(configdir)
+        #print entry
+        
+    def updateFeed(self, configdir, expiryTime=24):
+        for entry in self.getEntries():
+            if not entry["downloaded"]:
+                try:
+                    f = urllib2.urlopen(entry["link"])
+                    entry["summary"] = f.read()
+                    f.close()
+                    entry["downloaded"] = True
+                    entry["time"] = time.time()
+                except:
+                    pass
+            currentTime = time.time()
+            expiry = float(expiryTime) * 3600
+            if currentTime - entry["time"] > expiry:
+                self.entries.remove(entry)
+        self.saveFeed(configdir)
+
+    def getArticle(self, index):
+        self.setEntryRead(index)
+        content = self.getContent(index)
+        return content
+
 
 class Listing:
     # Lists all the feeds in a dictionary, and expose the data
-    def __init__(self):
+    def __init__(self, configdir):
+        self.configdir = configdir
         self.feeds = {}
-        if isfile(CONFIGDIR+"feeds.pickle"):
-            file = open(CONFIGDIR+"feeds.pickle")
+        if isfile(self.configdir+"feeds.pickle"):
+            file = open(self.configdir+"feeds.pickle")
             self.listOfFeeds = pickle.load(file)
             file.close()
         else:
             self.listOfFeeds = {getId("Slashdot"):{"title":"Slashdot", "url":"http://rss.slashdot.org/Slashdot/slashdot"}, }
-        if not self.listOfFeeds.has_key("font"):
-            self.setFont("16")
+        if self.listOfFeeds.has_key("font"):
+            del self.listOfFeeds["font"]
         if self.listOfFeeds.has_key("feedingit-order"):
             self.sortedKeys = self.listOfFeeds["feedingit-order"]
         else:
@@ -176,22 +232,22 @@ class Listing:
             except:
                 self.sortedKeys.remove(key)
         #self.saveConfig()
-        
-    def getFontSize(self):
-        return self.listOfFeeds["font"]
-    
-    def getReadFont(self):
-        return "sans %s" % self.listOfFeeds["font"]
-    
-    def getUnreadFont(self):
-        return "sans bold %s" % self.listOfFeeds["font"]
-    
-    def setFont(self, fontname):
-        self.listOfFeeds["font"] = fontname
+
+    def addArchivedArticle(self, key, index):
+        title = self.getFeed(key).getTitle(index)
+        link = self.getFeed(key).getLink(index)
+        date = self.getFeed(key).getDate(index)
+        if not self.listOfFeeds.has_key(getId("Archived Articles")):
+            self.listOfFeeds[getId("Archived Articles")] = {"title":"Archived Articles", "url":""}
+            self.sortedKeys.append(getId("Archived Articles"))
+            self.feeds[getId("Archived Articles")] = ArchivedArticles("Archived Articles", "")
+            self.saveConfig()
+            
+        self.getFeed(getId("Archived Articles")).addArchivedArticle(title, link, date, self.configdir)
         
     def loadFeed(self, key):
-            if isfile(CONFIGDIR+key):
-                file = open(CONFIGDIR+key)
+            if isfile(self.configdir+key):
+                file = open(self.configdir+key)
                 self.feeds[key] = pickle.load(file)
                 file.close()
             else:
@@ -199,12 +255,12 @@ class Listing:
                 url = self.listOfFeeds[key]["url"]
                 self.feeds[key] = Feed(title, url)
         
-    def updateFeeds(self):
+    def updateFeeds(self, expiryTime=24):
         for key in self.getListOfFeeds():
-            self.feeds[key].updateFeed()
+            self.feeds[key].updateFeed(self.configdir, expiryTime)
             
-    def updateFeed(self, key):
-        self.feeds[key].updateFeed()
+    def updateFeed(self, key, expiryTime=24):
+        self.feeds[key].updateFeed(self.configdir, expiryTime)
             
     def getFeed(self, key):
         return self.feeds[key]
@@ -235,12 +291,12 @@ class Listing:
         del self.listOfFeeds[key]
         self.sortedKeys.remove(key)
         del self.feeds[key]
-        if isfile(CONFIGDIR+key):
-           remove(CONFIGDIR+key)
+        if isfile(self.configdir+key):
+           remove(self.configdir+key)
     
     def saveConfig(self):
         self.listOfFeeds["feedingit-order"] = self.sortedKeys
-        file = open(CONFIGDIR+"feeds.pickle", "w")
+        file = open(self.configdir+"feeds.pickle", "w")
         pickle.dump(self.listOfFeeds, file)
         file.close()