439589e0fd74b676d58a5f8d6d92f1f2cd784035
[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.2.2
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):
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(newDisp)
495             del tmp
496             newDisp.show_all()
497             #stack.push(tmp)
498             #if not self.disp == False:
499             #   self.disp.destroyWindow()
500         else:
501             if not self.disp == False:
502                 #stack.pop(1)
503                 stack.pop_and_push(1,newDisp)
504             else:
505                 stack.push(newDisp)
506             #newDisp.show_all()
507             #if not self.disp == False:
508                 #self.disp.destroyWindow()
509         self.disp = newDisp
510
511     def nextArticle(self, object, index):
512         label = self.buttons[index].child
513         label.modify_font(pango.FontDescription(self.config.getReadFont()))
514         index = (index+1) % self.feed.getNumberOfEntries()
515         self.button_clicked(object, index)
516
517     def previousArticle(self, object, index):
518         label = self.buttons[index].child
519         label.modify_font(pango.FontDescription(self.config.getReadFont()))
520         index = (index-1) % self.feed.getNumberOfEntries()
521         self.button_clicked(object, index, True)
522
523     def onArticleClosed(self, object, index):
524         label = self.buttons[index].child
525         label.modify_font(pango.FontDescription(self.config.getReadFont()))
526         self.buttons[index].show()
527
528     def button_update_clicked(self, button):
529         disp = DownloadDialog(self, self.listing, [self.key,], self.config )       
530         #self.feed.updateFeed()
531         self.clear()
532         self.displayFeed()
533         
534     def buttonReadAllClicked(self, button):
535         for index in range(self.feed.getNumberOfEntries()):
536             self.feed.setEntryRead(index)
537             label = self.buttons[index].child
538             label.modify_font(pango.FontDescription(self.config.getReadFont()))
539             self.buttons[index].show()
540
541
542 class FeedingIt:
543     def __init__(self):
544         self.listing = Listing(CONFIGDIR)
545         
546         # Init the windows
547         self.window = hildon.StackableWindow()
548         self.config = Config(self.window, CONFIGDIR+"config.ini")
549         self.window.set_title("FeedingIt")
550         FremantleRotation("FeedingIt", main_window=self.window)
551         menu = hildon.AppMenu()
552         # Create a button and add it to the menu
553         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
554         button.set_label("Update All Feeds")
555         button.connect("clicked", self.button_update_clicked, "All")
556         menu.append(button)
557         
558         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
559         button.set_label("Add Feed")
560         button.connect("clicked", self.button_add_clicked)
561         menu.append(button)
562         
563         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
564         button.set_label("Organize Feeds")
565         button.connect("clicked", self.button_organize_clicked)
566         menu.append(button)
567
568         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
569         button.set_label("Preferences")
570         button.connect("clicked", self.button_preferences_clicked)
571         menu.append(button)
572        
573         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
574         button.set_label("Import Feeds")
575         button.connect("clicked", self.button_import_clicked)
576         menu.append(button)
577         
578         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
579         button.set_label("Export Feeds")
580         button.connect("clicked", self.button_export_clicked)
581         menu.append(button)
582         
583         self.window.set_app_menu(menu)
584         menu.show_all()
585         
586         self.feedWindow = hildon.StackableWindow()
587         self.articleWindow = hildon.StackableWindow()
588
589         self.displayListing()
590         self.autoupdate = False
591         self.checkAutoUpdate()
592
593
594     def button_export_clicked(self, button):
595         opml = ExportOpmlData(self.window, self.listing)
596         
597     def button_import_clicked(self, button):
598         opml = GetOpmlData(self.window)
599         feeds = opml.getData()
600         for (title, url) in feeds:
601             self.listing.addFeed(title, url)
602         self.displayListing()
603
604     def button_organize_clicked(self, button):
605         org = SortList(self.window, self.listing)
606         org.run()
607         org.destroy()
608         self.listing.saveConfig()
609         self.displayListing()
610         
611     def button_add_clicked(self, button, urlIn="http://"):
612         wizard = AddWidgetWizard(self.window, urlIn)
613         ret = wizard.run()
614         if ret == 2:
615             (title, url) = wizard.getData()
616             if (not title == '') and (not url == ''): 
617                self.listing.addFeed(title, url)
618         wizard.destroy()
619         self.displayListing()
620         
621     def button_update_clicked(self, button, key):
622         disp = DownloadDialog(self.window, self.listing, self.listing.getListOfFeeds(), self.config )           
623         self.displayListing()
624
625     def button_preferences_clicked(self, button):
626         dialog = self.config.createDialog()
627         dialog.connect("destroy", self.checkAutoUpdate)
628
629     def show_confirmation_note(self, parent, title):
630         note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
631
632         retcode = gtk.Dialog.run(note)
633         note.destroy()
634         
635         if retcode == gtk.RESPONSE_OK:
636             return True
637         else:
638             return False
639         
640     def displayListing(self):
641         try:
642             self.window.remove(self.pannableListing)
643         except:
644             pass
645         self.vboxListing = gtk.VBox(False,10)
646         self.pannableListing = hildon.PannableArea()
647         self.pannableListing.add_with_viewport(self.vboxListing)
648
649         self.buttons = {}
650         for key in self.listing.getListOfFeeds():
651             #button = gtk.Button(item)
652             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
653                               hildon.BUTTON_ARRANGEMENT_VERTICAL)
654             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
655                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
656             button.set_alignment(0,0,1,1)
657             button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
658             self.vboxListing.pack_start(button, expand=False)
659             self.buttons[key] = button
660         self.window.add(self.pannableListing)
661         self.window.show_all()
662
663     def buttonFeedClicked(widget, button, self, window, key):
664         disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key, self.config)
665         disp.connect("feed-closed", self.onFeedClosed)
666
667     def onFeedClosed(self, object, key):
668         self.displayListing()
669         #self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
670         #                    + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
671         #self.buttons[key].show()
672      
673     def run(self):
674         self.window.connect("destroy", gtk.main_quit)
675         gtk.main()
676         self.listing.saveConfig()
677
678     def checkAutoUpdate(self, *widget):
679         if self.config.isAutoUpdateEnabled():
680             if not self.autoupdate:
681                 self.autoupdateId = gobject.timeout_add(int(self.config.getUpdateInterval()*3600000), self.automaticUpdate)
682                 self.autoupdate = True
683         else:
684             if self.autoupdate:
685                 gobject.source_remove(self.autoupdateId)
686                 self.autoupdate = False
687
688     def automaticUpdate(self, *widget):
689         # Need to check for internet connection
690         # If no internet connection, try again in 10 minutes:
691         # gobject.timeout_add(int(5*3600000), self.automaticUpdate)
692         self.button_update_clicked(None, None)
693         return True
694
695 if __name__ == "__main__":
696     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
697     gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
698     gobject.signal_new("article-next", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
699     gobject.signal_new("article-previous", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
700     gobject.threads_init()
701     if not isdir(CONFIGDIR):
702         try:
703             mkdir(CONFIGDIR)
704         except:
705             print "Error: Can't create configuration directory"
706             sys.exit(1)
707     app = FeedingIt()
708     dbusHandler = ServerObject(app)
709     app.run()