Add support for Woodchuck.
[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(config, 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 Handler(BaseHTTPServer.BaseHTTPRequestHandler):
73     def updateAll(self):
74         for cat in listing.getListOfCategories():
75             for feed in listing.getSortedListOfKeys("Manual", category=cat):
76                 listing.updateFeed(feed)
77     
78     def openTaskSwitch(self):
79         import subprocess
80         subprocess.Popen("dbus-send /com/nokia/hildon_desktop com.nokia.hildon_desktop.exit_app_view", shell=True)
81     
82     def getCommands(self):
83         commandXml = "<commands>"
84         for item in commands:
85             if item[0]=="addFeed":
86                 commandXml += "<command c='addFeed'>%s</command>" %(sanitize(item[1]))
87             if item[0]=="openFeed":
88                 key = item[1]
89                 cat = str(listing.getFeedCategory(key))
90                 commandXml += "<command c='openFeed' cat='%s'>%s</command>" % (sanitize(cat), sanitize(key) )
91             if item[0]=="openArticle":
92                 key = item[1]
93                 cat = str(listing.getFeedCategory(key))
94                 articleid = item[2]
95                 commandXml += "<command c='openArticle' cat='%s' key='%s'>%s</command>" %(sanitize(cat), sanitize(key), sanitize(articleid) )
96             if item[0]=="updateAll":
97                 self.updateAll()
98             commands.remove(item)
99         commandXml += "</commands>"
100         return commandXml
101     
102     def getConfigXml(self):
103         xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><xml>"
104         xml += "<hideReadFeed>True</hideReadFeed>"
105         xml += "<hideReadArticles>True</hideReadArticles>"
106         xml += "</xml>"
107         return xml
108     
109     def generateCategoryXml(self):
110         xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><xml>"
111         for cat in listing.getListOfCategories():
112             xml += "<category>"
113             xml += "<catname>%s</catname>" %sanitize(listing.getCategoryTitle(cat))
114             xml += "<catid>%s</catid>" % cat
115             xml += "</category>"
116         xml += "</xml>"
117         return xml
118
119     def fix_title(self, title):
120         return escape(unescape(title).replace("<em>","").replace("</em>","").replace("<nobr>","").replace("</nobr>","").replace("<wbr>","").replace("&mdash;","-"))
121     
122     def generateFeedsXml(self, catid):
123         xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><xml>"
124         for key in listing.getSortedListOfKeys("Manual", category=catid):
125             xml += "<feed>"
126             xml += "<feedname>%s</feedname>" %sanitize(listing.getFeedTitle(key))
127             xml += "<feedid>%s</feedid>" %key
128             xml += "<unread>%s</unread>" %listing.getFeedNumberOfUnreadItems(key)
129             xml += "<updatedDate>%s</updatedDate>" %listing.getFeedUpdateTime(key)
130             xml += "<icon>%s</icon>" %listing.getFavicon(key)
131             if key in updatingFeeds:
132                 xml += "<updating>True</updating>"
133             else:
134                 xml += "<updating>False</updating>"
135             xml += "</feed>"
136         xml += "</xml>"
137         return xml
138     
139     def generateArticlesXml(self, key, onlyUnread, markAllAsRead):
140         feed = listing.getFeed(key)
141         if markAllAsRead=="True":
142             feed.markAllAsRead()
143             listing.updateUnread(key)
144             updateDbusHandler.ArticleCountUpdated()
145         xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><xml>"
146         if onlyUnread == "False":
147             onlyUnread = False
148         for id in feed.getIds(onlyUnread):
149             xml += "<article>"
150             xml += "<title>%s</title>" %self.fix_title(feed.getTitle(id))
151             xml += "<articleid>%s</articleid>" %id
152             xml += "<unread>%s</unread>" %str(feed.isEntryRead(id))
153             xml += "<updatedDate>%s</updatedDate>" %feed.getDateStamp(id)
154             xml += "<path>%s</path>" %feed.getContentLink(id)
155             xml += "</article>"
156         xml += "</xml>"
157         return xml
158
159     def do_GET(self):
160         (req, sep, arg) = self.path.partition("?")
161         request = req.split("/")
162         arguments = {}
163         if arg != "":
164             args = arg.split("&")
165             for arg in args:
166                 ele = arg.split("=")
167                 arguments[ele[0]] = ele[1]
168         if request[1] == "categories":
169             xml = self.generateCategoryXml()
170         elif request[1] == "feeds":
171             catid = request[2]
172             xml = self.generateFeedsXml(catid)
173         elif request[1] == "articles":
174             key = request[2]
175             onlyUnread = arguments.get("onlyUnread","False")
176             markAllAsRead = arguments.get("markAllAsRead", "False")
177             xml = self.generateArticlesXml(key, onlyUnread, markAllAsRead)
178         elif request[1] == "html":
179             key = request[2]
180             article = request[3]
181             feed = listing.getFeed(key)
182             try:
183                 file = open(feed.getContentLink(article))
184                 html = file.read().replace("body", "body bgcolor='#ffffff'", 1)
185                 file.close()
186             except:
187                 html = "<html><body>Error retrieving article</body></html>"
188             self.send_response(200)
189             self.send_header("Content-type", "text/html")
190             self.end_headers()
191             self.wfile.write(html)
192             #listing.updateUnread(key)
193             return
194         elif request[1] == "isUpdating":
195             xml = "<xml>"
196             key = request[2]
197             if (key in updatingFeeds) or ((key=="") and (len(updatingFeeds)>0)):
198                 xml += "<updating>True</updating>"
199             else:
200                 xml += "<updating>False</updating>"
201             xml += self.getCommands()
202             xml += "</xml>"
203         elif request[1] == "read":
204             key = request[2]
205             article = request[3]
206             feed = listing.getFeed(key)
207             feed.setEntryRead(article)
208             listing.updateUnread(key)
209             updateDbusHandler.ArticleCountUpdated()
210             self.send_response(200)
211             self.send_header("Content-type", "text/html")
212             self.end_headers()
213             self.wfile.write("OK")
214             return
215         elif request[1] == "config":
216             xml = self.getConfigXml()
217         elif request[1] == "home":
218             file = open(self.path)
219             self.send_response(200)
220             self.send_header("Content-type", "text/html")
221             self.end_headers()
222             self.wfile.write(file.read())
223             file.close()
224             return
225         elif request[1] == "task":
226             self.openTaskSwitch()
227             xml = "<xml>OK</xml>"
228         elif request[1] == "deleteCat":
229             key = request[2]
230             listing.removeCategory(key)
231             xml = "<xml>OK</xml>"
232         elif request[1] == "deleteFeed":
233             key = request[3]
234             listing.removeFeed(key)
235             xml = "<xml>OK</xml>"
236         elif request[1] == "addFeed":
237             cat = request[2]
238             name = request[3]
239             url = arguments.get("url","")
240             listing.addFeed(name, url, category=cat)
241             xml = "<xml>OK</xml>"
242         elif request[1] == "updateFeed":
243             key = request[2]
244             download = Download(listing, [key,])
245             download.start()
246             xml = "<xml>OK</xml>"
247         elif request[1]=="updateAll":
248             #app.automaticUpdate()
249             self.updateAll()
250             updateDbusHandler.ArticleCountUpdated()
251             xml = "<xml>OK</xml>"
252         elif request[1] == "addCat":
253             catName = request[2]
254             listing.addCategory(catName)
255             xml = "<xml>OK</xml>"
256         else:
257             self.send_error(404, "File not found")
258             return
259         self.send_response(200)
260         self.send_header("Content-type", "text/xml")
261         self.end_headers()
262         self.wfile.write(xml.encode("utf-8"))
263
264 PORT = 8000
265
266 if not isdir(CONFIGDIR):
267     try:
268         mkdir(CONFIGDIR)
269     except:
270         print "Error: Can't create configuration directory"
271         from sys import exit
272         exit(1)
273
274 from config import Config
275 config = Config(None,CONFIGDIR+"config.ini")
276
277 import thread
278
279
280
281 # Initialize the glib mainloop, for dbus purposes
282 from feedingitdbus import ServerObject
283 from updatedbus import UpdateServerObject, get_lock
284
285 import gobject
286 gobject.threads_init()
287 import dbus.mainloop.glib
288 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
289 import mainthread
290 mainthread.init()
291 from jobmanager import JobManager
292 JobManager(True)
293
294 global updateDbusHandler, dbusHandler
295 app = App()
296 dbusHandler = ServerObject(app)
297 updateDbusHandler = UpdateServerObject(app)
298
299 # Start the HTTP server in a new thread
300 thread.start_new_thread(start_server, ())
301
302 mainloop = gobject.MainLoop()
303 mainloop.run()