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