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