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