7f6b2a95e425457d4e43f41e79c360801a4e321a
[feedingit] / src / FeedingIt-Web.py
1 import BaseHTTPServer
2 import sys
3 from rss_sqlite import Listing
4 from xml import sax
5 from cgi import escape
6 from re import sub
7 from htmlentitydefs import name2codepoint
8 from gconf import client_get_default
9 from urllib2 import ProxyHandler
10 from threading import Thread
11 from os.path import isfile, isdir, exists
12 from os import mkdir, remove, stat
13
14 CONFIGDIR = "/home/user/.feedingit/"
15
16 updatingFeeds = []
17 #commands = [("addFeed","httpwww"), ("openFeed", "xxxx"), ("openArticle", ("feedid","artid"))]
18 commands = []
19
20 def unescape(text):
21     def fixup(m):
22         text = m.group(0)
23         if text[:2] == "&#":
24             # character reference
25             try:
26                 if text[:3] == "&#x":
27                     return unichr(int(text[3:-1], 16))
28                 else:
29                     return unichr(int(text[2:-1]))
30             except ValueError:
31                 pass
32         else:
33             # named entity
34             try:
35                 text = unichr(name2codepoint[text[1:-1]])
36             except KeyError:
37                 pass
38         return text # leave as is
39     return sub("&#?\w+;", fixup, text)
40
41 def sanitize(text):
42     from cgi import escape
43     return escape(text).encode('ascii', 'xmlcharrefreplace')
44
45 def start_server():
46     global listing
47     listing = Listing(CONFIGDIR)
48     httpd = BaseHTTPServer.HTTPServer(("127.0.0.1", PORT), Handler)
49     httpd.serve_forever()
50
51 class App():
52     def addFeed(self, url):
53         commands.append(("addFeed",url))
54     
55     def getStatus(self):
56         pass
57     
58     def openFeed(self, key):
59         commands.append( ("openFeed", key) )
60     
61     def openArticle(self, key, id):
62         commands.append( ("openArticle", key, id) )
63         
64     def automaticUpdate(self):
65         commands.append(("updateAll",))
66 #        for cat in listing.getListOfCategories():
67 #            for feed in listing.getSortedListOfKeys("Manual", category=cat):
68 #                feeds.append(feed)
69 #        download = Download(listing, feeds)
70 #        download.start()
71     
72 class Download(Thread):
73     def __init__(self, listing, keys):
74         Thread.__init__(self)
75         self.listing = listing
76         self.keys = keys
77         
78     def run (self):
79         updateDbusHandler.UpdateStarted()
80         for key in self.keys:
81             print "Start update: %s" % key
82             updatingFeeds.append(key)
83             (use_proxy, proxy) = config.getProxy()
84             try:
85                 if use_proxy:
86                         self.listing.updateFeed(key, proxy=proxy, imageCache=config.getImageCache() )
87                 else:
88                         self.listing.updateFeed(key, imageCache=config.getImageCache() )
89             except:
90                 print "Error updating feed: %s" %key
91             updatingFeeds.remove(key)
92             print "End update: %s" % key
93         updateDbusHandler.UpdateFinished()
94
95 class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
96     def updateAll(self):
97         feeds = []
98         for cat in listing.getListOfCategories():
99             for feed in listing.getSortedListOfKeys("Manual", category=cat):
100                 feeds.append(feed)
101         download = Download(listing, feeds)
102         download.start()
103     
104     def openTaskSwitch(self):
105         import subprocess
106         subprocess.Popen("dbus-send /com/nokia/hildon_desktop com.nokia.hildon_desktop.exit_app_view", shell=True)
107     
108     def getCommands(self):
109         commandXml = "<commands>"
110         for item in commands:
111             if item[0]=="addFeed":
112                 commandXml += "<command c='addFeed'>%s</command>" %(sanitize(item[1]))
113             if item[0]=="openFeed":
114                 key = item[1]
115                 cat = str(listing.getFeedCategory(key))
116                 commandXml += "<command c='openFeed' cat='%s'>%s</command>" % (sanitize(cat), sanitize(key) )
117             if item[0]=="openArticle":
118                 key = item[1]
119                 cat = str(listing.getFeedCategory(key))
120                 articleid = item[2]
121                 commandXml += "<command c='openArticle' cat='%s' key='%s'>%s</command>" %(sanitize(cat), sanitize(key), sanitize(articleid) )
122             if item[0]=="updateAll":
123                 self.updateAll()
124             commands.remove(item)
125         commandXml += "</commands>"
126         return commandXml
127     
128     def getConfigXml(self):
129         xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><xml>"
130         xml += "<hideReadFeed>True</hideReadFeed>"
131         xml += "<hideReadArticles>True</hideReadArticles>"
132         xml += "</xml>"
133         return xml
134     
135     def generateCategoryXml(self):
136         xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><xml>"
137         for cat in listing.getListOfCategories():
138             xml += "<category>"
139             xml += "<catname>%s</catname>" %sanitize(listing.getCategoryTitle(cat))
140             xml += "<catid>%s</catid>" % cat
141             xml += "</category>"
142         xml += "</xml>"
143         return xml
144
145     def fix_title(self, title):
146         return escape(unescape(title).replace("<em>","").replace("</em>","").replace("<nobr>","").replace("</nobr>","").replace("<wbr>","").replace("&mdash;","-"))
147     
148     def generateFeedsXml(self, catid):
149         xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><xml>"
150         for key in listing.getSortedListOfKeys("Manual", category=catid):
151             xml += "<feed>"
152             xml += "<feedname>%s</feedname>" %sanitize(listing.getFeedTitle(key))
153             xml += "<feedid>%s</feedid>" %key
154             xml += "<unread>%s</unread>" %listing.getFeedNumberOfUnreadItems(key)
155             xml += "<updatedDate>%s</updatedDate>" %listing.getFeedUpdateTime(key)
156             xml += "<icon>%s</icon>" %listing.getFavicon(key)
157             if key in updatingFeeds:
158                 xml += "<updating>True</updating>"
159             else:
160                 xml += "<updating>False</updating>"
161             xml += "</feed>"
162         xml += "</xml>"
163         return xml
164     
165     def generateArticlesXml(self, key, onlyUnread, markAllAsRead):
166         feed = listing.getFeed(key)
167         if markAllAsRead=="True":
168             feed.markAllAsRead()
169             listing.updateUnread(key)
170             updateDbusHandler.ArticleCountUpdated()
171         xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><xml>"
172         if onlyUnread == "False":
173             onlyUnread = False
174         for id in feed.getIds(onlyUnread):
175             xml += "<article>"
176             xml += "<title>%s</title>" %self.fix_title(feed.getTitle(id))
177             xml += "<articleid>%s</articleid>" %id
178             xml += "<unread>%s</unread>" %str(feed.isEntryRead(id))
179             xml += "<updatedDate>%s</updatedDate>" %feed.getDateStamp(id)
180             xml += "<path>%s</path>" %feed.getContentLink(id)
181             xml += "</article>"
182         xml += "</xml>"
183         return xml
184
185     def do_GET(self):
186         (req, sep, arg) = self.path.partition("?")
187         request = req.split("/")
188         arguments = {}
189         if arg != "":
190             args = arg.split("&")
191             for arg in args:
192                 ele = arg.split("=")
193                 arguments[ele[0]] = ele[1]
194         if request[1] == "categories":
195             xml = self.generateCategoryXml()
196         elif request[1] == "feeds":
197             catid = request[2]
198             xml = self.generateFeedsXml(catid)
199         elif request[1] == "articles":
200             key = request[2]
201             onlyUnread = arguments.get("onlyUnread","False")
202             markAllAsRead = arguments.get("markAllAsRead", "False")
203             xml = self.generateArticlesXml(key, onlyUnread, markAllAsRead)
204         elif request[1] == "html":
205             key = request[2]
206             article = request[3]
207             feed = listing.getFeed(key)
208             try:
209                 file = open(feed.getContentLink(article))
210                 html = file.read().replace("body", "body bgcolor='#ffffff'", 1)
211                 file.close()
212             except:
213                 html = "<html><body>Error retrieving article</body></html>"
214             self.send_response(200)
215             self.send_header("Content-type", "text/html")
216             self.end_headers()
217             self.wfile.write(html)
218             #listing.updateUnread(key)
219             return
220         elif request[1] == "isUpdating":
221             xml = "<xml>"
222             key = request[2]
223             if (key in updatingFeeds) or ((key=="") and (len(updatingFeeds)>0)):
224                 xml += "<updating>True</updating>"
225             else:
226                 xml += "<updating>False</updating>"
227             xml += self.getCommands()
228             xml += "</xml>"
229         elif request[1] == "read":
230             key = request[2]
231             article = request[3]
232             feed = listing.getFeed(key)
233             feed.setEntryRead(article)
234             listing.updateUnread(key)
235             updateDbusHandler.ArticleCountUpdated()
236             self.send_response(200)
237             self.send_header("Content-type", "text/html")
238             self.end_headers()
239             self.wfile.write("OK")
240             return
241         elif request[1] == "config":
242             xml = self.getConfigXml()
243         elif request[1] == "home":
244             file = open(self.path)
245             self.send_response(200)
246             self.send_header("Content-type", "text/html")
247             self.end_headers()
248             self.wfile.write(file.read())
249             file.close()
250             return
251         elif request[1] == "task":
252             self.openTaskSwitch()
253             xml = "<xml>OK</xml>"
254         elif request[1] == "deleteCat":
255             key = request[2]
256             listing.removeCategory(key)
257             xml = "<xml>OK</xml>"
258         elif request[1] == "deleteFeed":
259             key = request[3]
260             listing.removeFeed(key)
261             xml = "<xml>OK</xml>"
262         elif request[1] == "addFeed":
263             cat = request[2]
264             name = request[3]
265             url = arguments.get("url","")
266             listing.addFeed(name, url, category=cat)
267             xml = "<xml>OK</xml>"
268         elif request[1] == "updateFeed":
269             key = request[2]
270             download = Download(listing, [key,])
271             download.start()
272             xml = "<xml>OK</xml>"
273         elif request[1]=="updateAll":
274             #app.automaticUpdate()
275             self.updateAll()
276             updateDbusHandler.ArticleCountUpdated()
277             xml = "<xml>OK</xml>"
278         elif request[1] == "addCat":
279             catName = request[2]
280             listing.addCategory(catName)
281             xml = "<xml>OK</xml>"
282         else:
283             self.send_error(404, "File not found")
284             return
285         self.send_response(200)
286         self.send_header("Content-type", "text/xml")
287         self.end_headers()
288         self.wfile.write(xml.encode("utf-8"))
289
290 PORT = 8000
291
292 if not isdir(CONFIGDIR):
293     try:
294         mkdir(CONFIGDIR)
295     except:
296         print "Error: Can't create configuration directory"
297         from sys import exit
298         exit(1)
299
300 from config import Config
301 config = Config(None,CONFIGDIR+"config.ini")
302
303 import thread
304
305
306
307 # Start the HTTP server in a new thread
308 thread.start_new_thread(start_server, ())
309
310 # Initialize the glib mainloop, for dbus purposes
311 from feedingitdbus import ServerObject
312 from updatedbus import UpdateServerObject, get_lock
313
314 import gobject
315 gobject.threads_init()
316 import dbus.mainloop.glib
317 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
318
319 global updateDbusHandler, dbusHandler
320 app = App()
321 dbusHandler = ServerObject(app)
322 updateDbusHandler = UpdateServerObject(app)
323
324 mainloop = gobject.MainLoop()
325 mainloop.run()