0.4.1-2: auto-update and testing of window animation
[feedingit] / src / FeedingIt.py
1 #!/usr/bin/env python2.5
2
3
4 # Copyright (c) 2007-2008 INdT.
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 #  This program is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #  GNU Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public License
16 #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 # ============================================================================
20 # Name        : FeedingIt.py
21 # Author      : Yves Marcoz
22 # Version     : 0.4.1
23 # Description : Simple RSS Reader
24 # ============================================================================
25
26 import gtk
27 import feedparser
28 import pango
29 import hildon
30 import gtkhtml2
31 import time
32 import dbus
33 import pickle
34 from os.path import isfile, isdir
35 from os import mkdir
36 import sys   
37 import urllib2
38 import gobject
39 from portrait import FremantleRotation
40 import threading
41 import thread
42 from feedingitdbus import ServerObject
43 from config import Config
44
45 from rss import *
46 from opml import GetOpmlData, ExportOpmlData
47    
48 import socket
49 timeout = 5
50 socket.setdefaulttimeout(timeout)
51
52 CONFIGDIR="/home/user/.feedingit/"
53
54 class AddWidgetWizard(hildon.WizardDialog):
55     
56     def __init__(self, parent, urlIn):
57         # Create a Notebook
58         self.notebook = gtk.Notebook()
59
60         self.nameEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
61         self.nameEntry.set_placeholder("Enter Feed Name")
62         vbox = gtk.VBox(False,10)
63         label = gtk.Label("Enter Feed Name:")
64         vbox.pack_start(label)
65         vbox.pack_start(self.nameEntry)
66         self.notebook.append_page(vbox, None)
67         
68         self.urlEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
69         self.urlEntry.set_placeholder("Enter a URL")
70         self.urlEntry.set_text(urlIn)
71         self.urlEntry.select_region(0,-1)
72         
73         vbox = gtk.VBox(False,10)
74         label = gtk.Label("Enter Feed URL:")
75         vbox.pack_start(label)
76         vbox.pack_start(self.urlEntry)
77         self.notebook.append_page(vbox, None)
78
79         labelEnd = gtk.Label("Success")
80         
81         self.notebook.append_page(labelEnd, None)      
82
83         hildon.WizardDialog.__init__(self, parent, "Add Feed", self.notebook)
84    
85         # Set a handler for "switch-page" signal
86         #self.notebook.connect("switch_page", self.on_page_switch, self)
87    
88         # Set a function to decide if user can go to next page
89         self.set_forward_page_func(self.some_page_func)
90    
91         self.show_all()
92         
93     def getData(self):
94         return (self.nameEntry.get_text(), self.urlEntry.get_text())
95         
96     def on_page_switch(self, notebook, page, num, dialog):
97         return True
98    
99     def some_page_func(self, nb, current, userdata):
100         # Validate data for 1st page
101         if current == 0:
102             return len(self.nameEntry.get_text()) != 0
103         elif current == 1:
104             # Check the url is not null, and starts with http
105             return ( (len(self.urlEntry.get_text()) != 0) and (self.urlEntry.get_text().lower().startswith("http")) )
106         elif current != 2:
107             return False
108         else:
109             return True
110
111 class GetImage(threading.Thread):
112     def __init__(self, url, stream):
113         threading.Thread.__init__(self)
114         self.url = url
115         self.stream = stream
116     
117     def run(self):
118         f = urllib2.urlopen(self.url)
119         data = f.read()
120         f.close()
121         self.stream.write(data)
122         self.stream.close()
123
124 class ImageDownloader():
125     def __init__(self):
126         self.images = []
127         self.downloading = False
128         
129     def queueImage(self, url, stream):
130         self.images.append((url, stream))
131         if not self.downloading:
132             self.downloading = True
133             gobject.timeout_add(50, self.checkQueue)
134         
135     def checkQueue(self):
136         for i in range(4-threading.activeCount()):
137             if len(self.images) > 0:
138                 (url, stream) = self.images.pop() 
139                 GetImage(url, stream).start()
140         if len(self.images)>0:
141             gobject.timeout_add(200, self.checkQueue)
142         else:
143             self.downloading=False
144             
145     def stopAll(self):
146         self.images = []
147             
148         
149 class Download(threading.Thread):
150     def __init__(self, listing, key, config):
151         threading.Thread.__init__(self)
152         self.listing = listing
153         self.key = key
154         self.config = config
155         
156     def run (self):
157         self.listing.updateFeed(self.key, self.config.getExpiry())
158
159         
160 class DownloadDialog():
161     def __init__(self, parent, listing, listOfKeys, config):
162         self.listOfKeys = listOfKeys[:]
163         self.listing = listing
164         self.total = len(self.listOfKeys)
165         self.config = config
166         self.current = 0
167         
168         if self.total>0:
169             self.progress = gtk.ProgressBar()
170             self.waitingWindow = hildon.Note("cancel", parent, "Downloading",
171                                  progressbar=self.progress)
172             self.progress.set_text("Downloading")
173             self.fraction = 0
174             self.progress.set_fraction(self.fraction)
175             # Create a timeout
176             self.timeout_handler_id = gobject.timeout_add(50, self.update_progress_bar)
177             self.waitingWindow.show_all()
178             response = self.waitingWindow.run()
179             self.listOfKeys = []
180             while threading.activeCount() > 1:
181                 # Wait for current downloads to finish
182                 time.sleep(0.1)
183             self.waitingWindow.destroy()
184         
185     def update_progress_bar(self):
186         #self.progress_bar.pulse()
187         if threading.activeCount() < 4:
188             x = threading.activeCount() - 1
189             k = len(self.listOfKeys)
190             fin = self.total - k - x
191             fraction = float(fin)/float(self.total) + float(x)/(self.total*2.)
192             #print x, k, fin, fraction
193             self.progress.set_fraction(fraction)
194
195             if len(self.listOfKeys)>0:
196                 self.current = self.current+1
197                 key = self.listOfKeys.pop()
198                 download = Download(self.listing, key, self.config)
199                 download.start()
200                 return True
201             elif threading.activeCount() > 1:
202                 return True
203             else:
204                 self.waitingWindow.destroy()
205                 return False 
206         return True
207     
208     
209 class SortList(gtk.Dialog):
210     def __init__(self, parent, listing):
211         gtk.Dialog.__init__(self, "Organizer",  parent)
212         self.listing = listing
213         
214         self.vbox2 = gtk.VBox(False, 10)
215         
216         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
217         button.set_label("Move Up")
218         button.connect("clicked", self.buttonUp)
219         self.vbox2.pack_start(button, expand=False, fill=False)
220         
221         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
222         button.set_label("Move Down")
223         button.connect("clicked", self.buttonDown)
224         self.vbox2.pack_start(button, expand=False, fill=False)
225         
226         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
227         button.set_label("Delete")
228         button.connect("clicked", self.buttonDelete)
229         self.vbox2.pack_start(button, expand=False, fill=False)
230         
231         #button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
232         #button.set_label("Done")
233         #button.connect("clicked", self.buttonDone)
234         #self.vbox.pack_start(button)
235         self.hbox2= gtk.HBox(False, 10)
236         self.pannableArea = hildon.PannableArea()
237         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
238         self.treeview = gtk.TreeView(self.treestore)
239         self.hbox2.pack_start(self.pannableArea, expand=True)
240         self.displayFeeds()
241         self.hbox2.pack_end(self.vbox2, expand=False)
242         self.set_default_size(-1, 600)
243         self.vbox.pack_start(self.hbox2)
244         
245         self.show_all()
246         #self.connect("destroy", self.buttonDone)
247         
248     def displayFeeds(self):
249         self.treeview.destroy()
250         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
251         self.treeview = gtk.TreeView()
252         
253         self.treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
254         hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview, gtk.HILDON_UI_MODE_EDIT)
255         self.refreshList()
256         self.treeview.append_column(gtk.TreeViewColumn('Feed Name', gtk.CellRendererText(), text = 0))
257
258         self.pannableArea.add(self.treeview)
259
260         #self.show_all()
261
262     def refreshList(self, selected=None, offset=0):
263         rect = self.treeview.get_visible_rect()
264         y = rect.y+rect.height
265         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
266         for key in self.listing.getListOfFeeds():
267             item = self.treestore.append([self.listing.getFeedTitle(key), key])
268             if key == selected:
269                 selectedItem = item
270         self.treeview.set_model(self.treestore)
271         if not selected == None:
272             self.treeview.get_selection().select_iter(selectedItem)
273             self.treeview.scroll_to_cell(self.treeview.get_model().get_path(selectedItem))
274         self.pannableArea.show_all()
275
276     def getSelectedItem(self):
277         (model, iter) = self.treeview.get_selection().get_selected()
278         if not iter:
279             return None
280         return model.get_value(iter, 1)
281
282     def findIndex(self, key):
283         after = None
284         before = None
285         found = False
286         for row in self.treestore:
287             if found:
288                 return (before, row.iter)
289             if key == list(row)[0]:
290                 found = True
291             else:
292                 before = row.iter
293         return (before, None)
294
295     def buttonUp(self, button):
296         key  = self.getSelectedItem()
297         if not key == None:
298             self.listing.moveUp(key)
299             self.refreshList(key, -10)
300
301     def buttonDown(self, button):
302         key = self.getSelectedItem()
303         if not key == None:
304             self.listing.moveDown(key)
305             self.refreshList(key, 10)
306
307     def buttonDelete(self, button):
308         key = self.getSelectedItem()
309         if not key == None:
310             self.listing.removeFeed(key)
311         self.refreshList()
312
313     def buttonDone(self, *args):
314         self.destroy()
315                
316
317 class DisplayArticle(hildon.StackableWindow):
318     def __init__(self, title, text, link, index, key, listing):
319         hildon.StackableWindow.__init__(self)
320         self.imageDownloader = ImageDownloader()
321         self.listing=listing
322         self.key = key
323         self.index = index
324         self.text = text
325         self.link = link
326         self.set_title(title)
327         self.images = []
328         
329         # Init the article display    
330         self.view = gtkhtml2.View()
331         self.pannable_article = hildon.PannableArea()
332         self.pannable_article.add(self.view)
333         #self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
334         self.gestureId = self.pannable_article.connect('horizontal-movement', self.gesture)
335         self.document = gtkhtml2.Document()
336         self.view.set_document(self.document)
337         self.document.connect("link_clicked", self._signal_link_clicked)
338         self.document.connect("request-url", self._signal_request_url)
339         self.document.clear()
340         self.document.open_stream("text/html")
341         self.document.write_stream(self.text)
342         self.document.close_stream()
343         
344         menu = hildon.AppMenu()
345         # Create a button and add it to the menu
346         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
347         button.set_label("Allow Horizontal Scrolling")
348         button.connect("clicked", self.horiz_scrolling_button)
349         menu.append(button)
350         
351         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
352         button.set_label("Open in Browser")
353         button.connect("clicked", self._signal_link_clicked, self.link)
354         menu.append(button)
355         
356         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
357         button.set_label("Add to Archived Articles")
358         button.connect("clicked", self.archive_button)
359         menu.append(button)
360         
361         self.set_app_menu(menu)
362         menu.show_all()
363         
364         self.add(self.pannable_article)
365         
366         self.pannable_article.show_all()
367
368         self.destroyId = self.connect("destroy", self.destroyWindow)
369         #self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
370
371     def gesture(self, widget, direction, startx, starty):
372         if (direction == 3):
373             self.emit("article-next", self.index)
374         if (direction == 2):
375             self.emit("article-previous", self.index)
376         #self.timeout_handler_id = gobject.timeout_add(200, self.destroyWindow)
377
378     def destroyWindow(self, *args):
379         self.disconnect(self.destroyId)
380         self.emit("article-closed", self.index)
381         self.imageDownloader.stopAll()
382         self.destroy()
383         
384     def horiz_scrolling_button(self, *widget):
385         self.pannable_article.disconnect(self.gestureId)
386         self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
387         
388     def archive_button(self, *widget):
389         # Call the listing.addArchivedArticle
390         self.listing.addArchivedArticle(self.key, self.index)
391         
392     #def reloadArticle(self, *widget):
393     #    if threading.activeCount() > 1:
394             # Image thread are still running, come back in a bit
395     #        return True
396     #    else:
397     #        for (stream, imageThread) in self.images:
398     #            imageThread.join()
399     #            stream.write(imageThread.data)
400     #            stream.close()
401     #        return False
402     #    self.show_all()
403
404     def _signal_link_clicked(self, object, link):
405         bus = dbus.SystemBus()
406         proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
407         iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
408         iface.open_new_window(link)
409
410     def _signal_request_url(self, object, url, stream):
411         #print url
412         self.imageDownloader.queueImage(url, stream)
413         #imageThread = GetImage(url)
414         #imageThread.start()
415         #self.images.append((stream, imageThread))
416
417
418 class DisplayFeed(hildon.StackableWindow):
419     def __init__(self, listing, feed, title, key, config):
420         hildon.StackableWindow.__init__(self)
421         self.listing = listing
422         self.feed = feed
423         self.feedTitle = title
424         self.set_title(title)
425         self.key=key
426         self.config = config
427         
428         self.disp = False
429         
430         menu = hildon.AppMenu()
431         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
432         button.set_label("Update Feed")
433         button.connect("clicked", self.button_update_clicked)
434         menu.append(button)
435         
436         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
437         button.set_label("Mark All As Read")
438         button.connect("clicked", self.buttonReadAllClicked)
439         menu.append(button)
440         self.set_app_menu(menu)
441         menu.show_all()
442         
443         self.displayFeed()
444         
445         self.connect("destroy", self.destroyWindow)
446         
447     def destroyWindow(self, *args):
448         self.emit("feed-closed", self.key)
449         self.destroy()
450         self.feed.saveFeed(CONFIGDIR)
451
452     def displayFeed(self):
453         self.vboxFeed = gtk.VBox(False, 10)
454         self.pannableFeed = hildon.PannableArea()
455         self.pannableFeed.add_with_viewport(self.vboxFeed)
456         self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
457         self.buttons = []
458         for index in range(self.feed.getNumberOfEntries()):
459             button = gtk.Button(self.feed.getTitle(index))
460             button.set_alignment(0,0)
461             label = button.child
462             if self.feed.isEntryRead(index):
463                 #label.modify_font(pango.FontDescription("sans 16"))
464                 label.modify_font(pango.FontDescription(self.config.getReadFont()))
465             else:
466                 #print self.listing.getFont() + " bold"
467                 label.modify_font(pango.FontDescription(self.config.getUnreadFont()))
468                 #label.modify_font(pango.FontDescription("sans bold 23"))
469                 #"sans bold 16"
470             label.set_line_wrap(True)
471             
472             label.set_size_request(self.get_size()[0]-50, -1)
473             button.connect("clicked", self.button_clicked, index)
474             self.buttons.append(button)
475             
476             self.vboxFeed.pack_start(button, expand=False)           
477             index=index+1
478
479         self.add(self.pannableFeed)
480         self.show_all()
481         
482     def clear(self):
483         self.remove(self.pannableFeed)
484
485     def button_clicked(self, button, index, previous=False, next=False):
486         newDisp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), self.feed.getLink(index), index, self.key, self.listing)
487         self.ids = []
488         self.ids.append(newDisp.connect("article-closed", self.onArticleClosed))
489         self.ids.append(newDisp.connect("article-next", self.nextArticle))
490         self.ids.append(newDisp.connect("article-previous", self.previousArticle))
491         stack = hildon.WindowStack.get_default()
492         if previous:
493             tmp = stack.pop(1)
494             stack.push_list([newDisp, tmp[0]])
495             #del tmp
496             newDisp.show_all()
497             gobject.timeout_add(500, self.destroyArticle, tmp[0])
498             #print "previous"
499             self.disp = newDisp
500             
501             #stack.push(tmp)
502             #if not self.disp == False:
503             #   self.disp.destroyWindow()
504         elif next:
505             #print type(self.disp).__name__
506
507                 #self.disp.destroyWindow()
508                 #stack.pop_and_push(1,newDisp)
509             #else:
510             #    stack.push(newDisp)
511             #self.disp = newDisp
512             newDisp.show_all()
513             if type(self.disp).__name__ == "DisplayArticle":
514                 #tmp = stack.pop(2)
515                 #stack.push(self.disp)
516                 #stack.pop_and_push(1)
517                 #self.disp.hide()
518                 #print "Done"
519                 gobject.timeout_add(500, self.destroyArticle, self.disp)
520             self.disp = newDisp
521             #self.disp.show_all()
522             #if not self.disp == False:
523             #    self.disp.destroyWindow()
524         else:
525             self.disp = newDisp
526             self.disp.show_all()
527
528     def destroyArticle(self, handle):
529         handle.destroyWindow()
530
531     def nextArticle(self, object, index):
532         label = self.buttons[index].child
533         label.modify_font(pango.FontDescription(self.config.getReadFont()))
534         index = (index+1) % self.feed.getNumberOfEntries()
535         self.button_clicked(object, index, next=True)
536
537     def previousArticle(self, object, index):
538         label = self.buttons[index].child
539         label.modify_font(pango.FontDescription(self.config.getReadFont()))
540         index = (index-1) % self.feed.getNumberOfEntries()
541         self.button_clicked(object, index, previous=True)
542
543     def onArticleClosed(self, object, index):
544         label = self.buttons[index].child
545         label.modify_font(pango.FontDescription(self.config.getReadFont()))
546         self.buttons[index].show()
547
548     def button_update_clicked(self, button):
549         disp = DownloadDialog(self, self.listing, [self.key,], self.config )       
550         #self.feed.updateFeed()
551         self.clear()
552         self.displayFeed()
553         
554     def buttonReadAllClicked(self, button):
555         for index in range(self.feed.getNumberOfEntries()):
556             self.feed.setEntryRead(index)
557             label = self.buttons[index].child
558             label.modify_font(pango.FontDescription(self.config.getReadFont()))
559             self.buttons[index].show()
560
561
562 class FeedingIt:
563     def __init__(self):
564         self.listing = Listing(CONFIGDIR)
565         
566         # Init the windows
567         self.window = hildon.StackableWindow()
568         self.config = Config(self.window, CONFIGDIR+"config.ini")
569         self.window.set_title("FeedingIt")
570         FremantleRotation("FeedingIt", main_window=self.window)
571         menu = hildon.AppMenu()
572         # Create a button and add it to the menu
573         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
574         button.set_label("Update All Feeds")
575         button.connect("clicked", self.button_update_clicked, "All")
576         menu.append(button)
577         
578         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
579         button.set_label("Add Feed")
580         button.connect("clicked", self.button_add_clicked)
581         menu.append(button)
582         
583         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
584         button.set_label("Organize Feeds")
585         button.connect("clicked", self.button_organize_clicked)
586         menu.append(button)
587
588         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
589         button.set_label("Preferences")
590         button.connect("clicked", self.button_preferences_clicked)
591         menu.append(button)
592        
593         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
594         button.set_label("Import Feeds")
595         button.connect("clicked", self.button_import_clicked)
596         menu.append(button)
597         
598         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
599         button.set_label("Export Feeds")
600         button.connect("clicked", self.button_export_clicked)
601         menu.append(button)
602         
603         self.window.set_app_menu(menu)
604         menu.show_all()
605         
606         self.feedWindow = hildon.StackableWindow()
607         self.articleWindow = hildon.StackableWindow()
608
609         self.displayListing()
610         self.autoupdate = False
611         self.checkAutoUpdate()
612
613
614     def button_export_clicked(self, button):
615         opml = ExportOpmlData(self.window, self.listing)
616         
617     def button_import_clicked(self, button):
618         opml = GetOpmlData(self.window)
619         feeds = opml.getData()
620         for (title, url) in feeds:
621             self.listing.addFeed(title, url)
622         self.displayListing()
623
624     def button_organize_clicked(self, button):
625         org = SortList(self.window, self.listing)
626         org.run()
627         org.destroy()
628         self.listing.saveConfig()
629         self.displayListing()
630         
631     def button_add_clicked(self, button, urlIn="http://"):
632         wizard = AddWidgetWizard(self.window, urlIn)
633         ret = wizard.run()
634         if ret == 2:
635             (title, url) = wizard.getData()
636             if (not title == '') and (not url == ''): 
637                self.listing.addFeed(title, url)
638         wizard.destroy()
639         self.displayListing()
640         
641     def button_update_clicked(self, button, key):
642         disp = DownloadDialog(self.window, self.listing, self.listing.getListOfFeeds(), self.config )           
643         self.displayListing()
644
645     def button_preferences_clicked(self, button):
646         dialog = self.config.createDialog()
647         dialog.connect("destroy", self.checkAutoUpdate)
648
649     def show_confirmation_note(self, parent, title):
650         note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
651
652         retcode = gtk.Dialog.run(note)
653         note.destroy()
654         
655         if retcode == gtk.RESPONSE_OK:
656             return True
657         else:
658             return False
659         
660     def displayListing(self):
661         try:
662             self.window.remove(self.pannableListing)
663         except:
664             pass
665         self.vboxListing = gtk.VBox(False,10)
666         self.pannableListing = hildon.PannableArea()
667         self.pannableListing.add_with_viewport(self.vboxListing)
668
669         self.buttons = {}
670         for key in self.listing.getListOfFeeds():
671             #button = gtk.Button(item)
672             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
673                               hildon.BUTTON_ARRANGEMENT_VERTICAL)
674             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
675                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
676             button.set_alignment(0,0,1,1)
677             button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
678             self.vboxListing.pack_start(button, expand=False)
679             self.buttons[key] = button
680         self.window.add(self.pannableListing)
681         self.window.show_all()
682
683     def buttonFeedClicked(widget, button, self, window, key):
684         disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key, self.config)
685         disp.connect("feed-closed", self.onFeedClosed)
686
687     def onFeedClosed(self, object, key):
688         self.displayListing()
689         #self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
690         #                    + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
691         #self.buttons[key].show()
692      
693     def run(self):
694         self.window.connect("destroy", gtk.main_quit)
695         gtk.main()
696         self.listing.saveConfig()
697
698     def checkAutoUpdate(self, *widget):
699         if self.config.isAutoUpdateEnabled():
700             if not self.autoupdate:
701                 self.autoupdateId = gobject.timeout_add(int(self.config.getUpdateInterval()*3600000), self.automaticUpdate)
702                 self.autoupdate = True
703         else:
704             if self.autoupdate:
705                 gobject.source_remove(self.autoupdateId)
706                 self.autoupdate = False
707
708     def automaticUpdate(self, *widget):
709         # Need to check for internet connection
710         # If no internet connection, try again in 10 minutes:
711         # gobject.timeout_add(int(5*3600000), self.automaticUpdate)
712         self.button_update_clicked(None, None)
713         return True
714
715 if __name__ == "__main__":
716     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
717     gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
718     gobject.signal_new("article-next", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
719     gobject.signal_new("article-previous", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
720     gobject.threads_init()
721     if not isdir(CONFIGDIR):
722         try:
723             mkdir(CONFIGDIR)
724         except:
725             print "Error: Can't create configuration directory"
726             sys.exit(1)
727     app = FeedingIt()
728     dbusHandler = ServerObject(app)
729     app.run()