Initial setup of the repository
[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.1
23 # Description : PyGtk Example 
24 # ============================================================================
25
26 import gtk
27 import feedparser
28 import pango
29 import hildon
30 import gtkhtml2
31 import time
32 import webbrowser
33 import pickle
34 from os.path import isfile, isdir
35 from os import mkdir
36 import md5
37 import sys
38
39 CONFIGDIR="/home/user/.feedingit/"
40
41 def getId(string):
42     return md5.new(string).hexdigest()
43
44 class Feed:
45     # Contains all the info about a single feed (articles, ...), and expose the data
46     def __init__(self, name, url):
47         self.feed = []
48         self.name = name
49         self.url = url
50         self.updateTime = "Never"
51         #self.feed=feedparser.parse(url)
52     
53     def updateFeed(self):
54         self.feed=feedparser.parse(self.url)
55         self.updateTime = time.asctime()
56         file = open(CONFIGDIR+getId(self.name), "w")
57         pickle.dump(self, file )
58         file.close()
59     
60     def getUpdateTime(self):
61         return self.updateTime
62     
63     def getEntries(self):
64         try:
65             return self.feed["entries"]
66         except:
67             return []
68     
69     def getItem(self, index):
70         try:
71             return self.feed["entries"][index]
72         except:
73             return []
74     
75     def getArticle(self, index):
76         entry = self.feed["entries"][index]
77         text = "<h4><a href=\"" + entry["link"] + "\">" + entry["title"] + "</a></h4>"
78         text = text + "<small><i>Date: " + time.strftime("%a, %d %b %Y %H:%M:%S",entry["updated_parsed"]) + "</i></small>"
79         text = text + "<BR />"
80         text = text + entry["summary"]
81         return text    
82     
83 class Listing:
84     # Lists all the feeds in a dictionary, and expose the data
85     
86     def updateFeeds(self):
87         for key in self.listOfFeeds.keys():
88             self.feeds[key].updateFeed()
89             
90     def getFeed(self, key):
91         return self.feeds[key]
92     
93     def getFeedUpdateTime(self, key):
94         return self.feeds[key].getUpdateTime()
95    
96     def getFeedTitle(self, key):
97         return self.listOfFeeds[key]["title"]
98     
99     def getFeedUrl(self, key):
100         return self.listOfFeeds[key]["url"]
101     
102     def getListOfFeeds(self):
103         return self.listOfFeeds.keys()
104     
105     def addFeed(self, title, url):
106         self.listOfFeeds[getId(title)] = {"title":title, "url":url}
107         self.saveConfig()
108         self.feeds[getId(title)] = Feed(title, url)
109     
110     def saveConfig(self):
111         file = open(CONFIGDIR+"feeds.pickle", "w")
112         pickle.dump(self.listOfFeeds, file)
113         file.close()
114     
115     def __init__(self):
116         self.feeds = {}
117         if isfile(CONFIGDIR+"feeds.pickle"):
118             file = open(CONFIGDIR+"feeds.pickle")
119             self.listOfFeeds = pickle.load(file)
120             file.close()
121         else:
122             self.listOfFeeds = {getId("Slashdot"):{"title":"Slashdot", "url":"http://rss.slashdot.org/Slashdot/slashdot"}, }
123         for key in self.listOfFeeds.keys():
124             if isfile(CONFIGDIR+key):
125                 file = open(CONFIGDIR+key)
126                 self.feeds[key] = pickle.load(file)
127                 file.close()
128             else:
129                 self.feeds[key] = Feed(self.listOfFeeds[key]["title"], self.listOfFeeds[key]["url"])
130         self.saveConfig()
131    
132    
133 class AddWidgetWizard(hildon.WizardDialog):
134     
135     def __init__(self, parent):
136         # Create a Notebook
137         self.notebook = gtk.Notebook()
138
139         self.nameEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
140         self.nameEntry.set_placeholder("Enter Feed Name")
141         
142         self.urlEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
143         
144         self.urlEntry.set_placeholder("Enter a URL")
145             
146         labelEnd = gtk.Label("Success")
147         
148         self.notebook.append_page(self.nameEntry, None)
149         self.notebook.append_page(self.urlEntry, None) 
150         self.notebook.append_page(labelEnd, None)      
151
152         hildon.WizardDialog.__init__(self, parent, "Add Feed", self.notebook)
153    
154         # Set a handler for "switch-page" signal
155         #self.notebook.connect("switch_page", self.on_page_switch, self)
156    
157         # Set a function to decide if user can go to next page
158         self.set_forward_page_func(self.some_page_func)
159    
160         self.show_all()
161         print dir(self)
162         
163     def getData(self):
164         return (self.nameEntry.get_text(), self.urlEntry.get_text())
165         
166     def on_page_switch(self, notebook, page, num, dialog):
167         print >>sys.stderr, "Page %d" % num
168         return True
169    
170     def some_page_func(self, nb, current, userdata):
171         # Validate data for 1st page
172         print current
173         if current == 0:
174             entry = nb.get_nth_page(current)
175             # Check the name is not null
176             return len(entry.get_text()) != 0
177         elif current == 1:
178             entry = nb.get_nth_page(current)
179             # Check the url is not null, and starts with http
180             print ( (len(entry.get_text()) != 0) and (entry.get_text().startswith("http")) )
181             return ( (len(entry.get_text()) != 0) and (entry.get_text().startswith("http")) )
182         elif current != 2:
183             return False
184         else:
185             return True
186
187 class FeedingIt:
188     def __init__(self):
189         # Init the windows
190         self.window = hildon.StackableWindow()
191         menu = hildon.AppMenu()
192         # Create a button and add it to the menu
193         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
194         button.set_label("Update All Feeds")
195         button.connect("clicked", self.button_update_clicked, "All")
196         menu.append(button)
197         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
198         button.set_label("Add Feed")
199         button.connect("clicked", self.button_add_clicked)
200         menu.append(button)
201         
202         self.window.set_app_menu(menu)
203         menu.show_all()
204         
205         self.feedWindow = hildon.StackableWindow()
206         self.articleWindow = hildon.StackableWindow()
207
208         self.listing = Listing()
209         #self.listing.downloadFeeds()
210         self.displayListing() 
211         
212         #self.window.show_all()
213         #self.displayFeed(self.listing.getFeed(0))
214         
215     def button_add_clicked(self, button):
216         wizard = AddWidgetWizard(self.window)
217         ret = wizard.run()
218         if ret == 2:
219             (title, url) = wizard.getData()
220             if (not title == '') and (not url == ''): 
221                self.listing.addFeed(title, url)
222         wizard.destroy()
223         self.displayListing()
224         
225     def button_update_clicked(self, button, key):
226         hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
227         if key == "All":
228             self.listing.updateFeeds()
229         else:
230             self.listing.getFeed(key).updateFeed()
231         self.displayListing()
232         hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
233         
234     def displayListing(self):
235         try:
236             self.window.remove(self.pannableListing)
237         except:
238             pass
239         self.vboxListing = gtk.VBox(False,10)
240         self.pannableListing = hildon.PannableArea()
241         self.pannableListing.add_with_viewport(self.vboxListing)
242
243         for key in self.listing.getListOfFeeds():
244             #button = gtk.Button(item)
245             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
246                               hildon.BUTTON_ARRANGEMENT_VERTICAL)
247             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key))
248             button.set_alignment(0,0,1,1)
249             #label = button.child
250             #label.modify_font(pango.FontDescription("sans 10"))
251             button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
252             self.vboxListing.pack_start(button, expand=False)
253         self.window.add(self.pannableListing)
254         self.window.show_all()
255         
256     def displayFeed(self, key):
257         # Initialize the feed panel
258         self.vboxFeed = gtk.VBox(False, 10)
259         self.pannableFeed = hildon.PannableArea()
260         self.pannableFeed.add_with_viewport(self.vboxFeed)
261         
262         index = 0
263         for item in self.listing.getFeed(key).getEntries():
264             #button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
265             #                  hildon.BUTTON_ARRANGEMENT_HORIZONTAL)
266             #button.set_text(item["title"], time.strftime("%a, %d %b %Y %H:%M:%S",item["updated_parsed"]))
267             #button.set_text(item["title"], time.asctime(item["updated_parsed"]))
268             #button.set_text(item["title"],"")
269             #button.set_alignment(0,0,1,1)
270             #button.set_markup(True)
271             button = gtk.Button(item["title"])
272             button.set_alignment(0,0)
273             label = button.child
274             #label.set_markup(item["title"])
275             label.modify_font(pango.FontDescription("sans 16"))
276             button.connect("clicked", self.button_clicked, self, self.window, key, index)
277             self.vboxFeed.pack_start(button, expand=False)
278             index=index+1
279
280         self.feedWindow.add(self.pannableFeed)
281         self.feedWindow.show_all()
282      
283     def displayArticle(self, key, index):
284         text = self.listing.getFeed(key).getArticle(index)
285         self.articleWindow = hildon.StackableWindow()
286         # Init the article display    
287         self.view = gtkhtml2.View()
288         self.document = gtkhtml2.Document()
289         self.view.set_document(self.document)
290         self.pannable_article = hildon.PannableArea()
291         
292         #self.view.connect("on_url", self._signal_on_url)
293         self.document.connect("link_clicked", self._signal_link_clicked)
294         #self.document.connect("request-url", self._signal_request_url)
295
296         self.document.clear()
297         self.document.open_stream("text/html")
298         self.document.write_stream(text)
299         self.document.close_stream()
300         
301         self.pannable_article.add_with_viewport(self.view)
302         self.articleWindow.add(self.pannable_article)
303         self.articleWindow.show_all()
304      
305 #    def _signal_on_url(self, object, url):
306 #        if url == None: url = ""
307 #        else: url = self._complete_url(url)
308         #self.emit("status_changed", url)
309
310     def _signal_link_clicked(self, object, link):
311         #self.emit("open_uri", self._complete_url(link))
312         #os.spawnl(os.P_NOWAIT, '/usr/bin/browser', '/usr/bin/browser', '--url', link)
313         webbrowser.open(link)
314
315 #    def _signal_request_url(self, object, url, stream):
316 #        stream.write(self._fetch_url(self._complete_url(url)))
317 #        
318 #    def _complete_url(self, url):
319 #        import string, urlparse, urllib
320 #        url = urllib.quote(url, safe=string.punctuation)
321 #        if urlparse.urlparse(url)[0] == '':
322 #            return urlparse.urljoin(self.location, url)
323 #        else:
324 #            return url
325 #        
326 #    def _open_url(self, url, headers=[]):
327 #        import urllib2
328 #        opener = urllib2.build_opener()
329 #        opener.addheaders = [('User-agent', 'Wikitin')]+headers
330 #        return opener.open(url)
331 #
332 #    def _fetch_url(self, url, headers=[]):
333 #        return self._open_url(url, headers).read()
334         
335         
336     def button_clicked(widget, button, app, window, key, index):
337         app.displayArticle(key, index)
338     
339     def buttonFeedClicked(widget, button, app, window, key):
340         app.displayFeed(key)
341      
342     def run(self):
343         self.window.connect("destroy", gtk.main_quit)
344         #self.window.show_all()
345         gtk.main()
346
347 if __name__ == "__main__":
348     if not isdir(CONFIGDIR):
349         try:
350             mkdir(CONFIGDIR)
351         except:
352             print "Error: Can't create configuration directory"
353             sys.exit(1)
354     app = FeedingIt()
355     app.run()