rev |
line source |
paulo@39
|
1 import os
|
paulo@40
|
2 import sys
|
paulo@39
|
3 import re
|
paulo@40
|
4 import urllib2
|
paulo@40
|
5 import threading
|
paulo@40
|
6 import Queue
|
paulo@41
|
7 import datetime
|
paulo@41
|
8 import time
|
paulo@42
|
9 import logging
|
paulo@42
|
10 logging.basicConfig(level=logging.INFO)
|
paulo@39
|
11
|
paulo@39
|
12 import html
|
paulo@39
|
13 import xml.etree.ElementTree
|
paulo@39
|
14
|
paulo@39
|
15
|
paulo@41
|
16 FEEDS_FILE = "FEEDS"
|
paulo@41
|
17 CACHE_HTML_FILE = "__cache__.html"
|
paulo@41
|
18
|
paulo@44
|
19 CACHE_LIFE = 1200 # [seconds]
|
paulo@39
|
20 MAX_ITEMS = 30
|
paulo@39
|
21 MAX_LINK_Z = 4
|
paulo@40
|
22 MAX_THREADS = 20
|
paulo@39
|
23
|
paulo@39
|
24
|
paulo@39
|
25 _PARSE_ROOT_TAG_RE = re.compile(r"(\{(.+)\})?(.+)")
|
paulo@39
|
26
|
paulo@39
|
27 def _parse_root_tag(root_tag):
|
paulo@39
|
28 re_match = _PARSE_ROOT_TAG_RE.match(root_tag)
|
paulo@39
|
29
|
paulo@39
|
30 if re_match is None:
|
paulo@39
|
31 return (None, None)
|
paulo@39
|
32 else:
|
paulo@39
|
33 return re_match.group(2, 3)
|
paulo@39
|
34
|
paulo@39
|
35
|
paulo@39
|
36 def _go_rss(elementTree):
|
paulo@39
|
37 title = elementTree.find("channel/title").text.strip()
|
paulo@39
|
38 link = elementTree.find("channel/link").text
|
paulo@39
|
39
|
paulo@39
|
40 items = []
|
paulo@39
|
41
|
paulo@39
|
42 for i in elementTree.findall("channel/item")[:MAX_ITEMS]:
|
paulo@39
|
43 it_title = i.find("title").text.strip()
|
paulo@39
|
44 it_link = i.find("link").text
|
paulo@39
|
45
|
paulo@39
|
46 items.append((it_title, it_link))
|
paulo@39
|
47
|
paulo@39
|
48 return (title, link, items)
|
paulo@39
|
49
|
paulo@39
|
50
|
paulo@39
|
51 def _go_atom(elementTree):
|
paulo@39
|
52 ns = "http://www.w3.org/2005/Atom"
|
paulo@39
|
53
|
paulo@39
|
54 title = elementTree.find("{%s}title" % ns).text.strip()
|
paulo@39
|
55 link = ''
|
paulo@39
|
56
|
paulo@39
|
57 for i in elementTree.findall("{%s}link" % ns):
|
paulo@39
|
58 if i.get("type") == "text/html" and i.get("rel") == "alternate":
|
paulo@39
|
59 link = i.get("href")
|
paulo@39
|
60 break
|
paulo@39
|
61
|
paulo@39
|
62 items = []
|
paulo@39
|
63
|
paulo@39
|
64 for i in elementTree.findall("{%s}entry" % ns)[:MAX_ITEMS]:
|
paulo@39
|
65 it_title = i.find("{%s}title" % ns).text.strip()
|
paulo@39
|
66 it_link = ''
|
paulo@39
|
67
|
paulo@39
|
68 for j in i.findall("{%s}link" % ns):
|
paulo@39
|
69 if j.get("type") == "text/html" and j.get("rel") == "alternate":
|
paulo@39
|
70 it_link = j.get("href")
|
paulo@39
|
71 break
|
paulo@39
|
72
|
paulo@39
|
73 items.append((it_title, it_link))
|
paulo@39
|
74
|
paulo@39
|
75 return (title, link, items)
|
paulo@39
|
76
|
paulo@39
|
77
|
paulo@41
|
78 def _to_html(dtnow, docstruct):
|
paulo@41
|
79 datetime_str = dtnow.strftime("%Y-%m-%d %H:%M %Z")
|
paulo@41
|
80 page_title = "myrss -- %s" % datetime_str
|
paulo@41
|
81
|
paulo@42
|
82 root = html.HTML("html")
|
paulo@39
|
83
|
paulo@39
|
84 header = root.header
|
paulo@41
|
85 header.title(page_title)
|
paulo@39
|
86 header.link(rel="stylesheet", type="text/css", href="index.css")
|
paulo@39
|
87
|
paulo@41
|
88 body = root.body
|
paulo@41
|
89 body.h1(page_title)
|
paulo@41
|
90
|
paulo@39
|
91 link_z = 0
|
paulo@39
|
92
|
paulo@39
|
93 for feed in docstruct:
|
paulo@40
|
94 if feed is None:
|
paulo@40
|
95 continue
|
paulo@40
|
96
|
paulo@39
|
97 (title, link, items) = feed
|
paulo@39
|
98
|
paulo@41
|
99 body.h2.a(title, href=link, klass="z%d" % (link_z % MAX_LINK_Z))
|
paulo@39
|
100 link_z += 1
|
paulo@41
|
101 p = body.p
|
paulo@39
|
102
|
paulo@39
|
103 for (i, (it_title, it_link)) in enumerate(items):
|
paulo@39
|
104 if i > 0:
|
paulo@39
|
105 p += " - "
|
paulo@39
|
106
|
paulo@39
|
107 p.a(it_title, href=it_link, klass="z%d" % (link_z % MAX_LINK_Z))
|
paulo@39
|
108 link_z += 1
|
paulo@39
|
109
|
paulo@39
|
110 return unicode(root).encode("utf-8")
|
paulo@39
|
111
|
paulo@39
|
112
|
paulo@40
|
113 def _process_url(url):
|
paulo@40
|
114 ret = None
|
paulo@40
|
115
|
paulo@40
|
116 try:
|
paulo@42
|
117 logging.info("processing %s" % url)
|
paulo@41
|
118 feed = urllib2.urlopen(urllib2.Request(url, headers={"User-Agent": ''}))
|
paulo@40
|
119 except urllib2.HTTPError as e:
|
paulo@42
|
120 logging.info("(%s) %s" % (url, e))
|
paulo@40
|
121 return ret
|
paulo@40
|
122
|
paulo@40
|
123 elementTree = xml.etree.ElementTree.parse(feed)
|
paulo@40
|
124 root = elementTree.getroot()
|
paulo@40
|
125
|
paulo@40
|
126 parsed_root_tag = _parse_root_tag(root.tag)
|
paulo@40
|
127
|
paulo@40
|
128 if parsed_root_tag == (None, "rss"):
|
paulo@40
|
129 version = float(root.get("version", 0.0))
|
paulo@40
|
130 if version >= 2.0:
|
paulo@40
|
131 ret = _go_rss(elementTree)
|
paulo@40
|
132 else:
|
paulo@40
|
133 raise NotImplementedError("Unsupported rss version")
|
paulo@40
|
134 elif parsed_root_tag == ("http://www.w3.org/2005/Atom", "feed"):
|
paulo@40
|
135 ret = _go_atom(elementTree)
|
paulo@40
|
136 else:
|
paulo@40
|
137 raise NotImplementedError("Unknown root tag")
|
paulo@40
|
138
|
paulo@40
|
139 return ret
|
paulo@40
|
140
|
paulo@40
|
141
|
paulo@40
|
142 class WorkerThread(threading.Thread):
|
paulo@40
|
143 def __init__(self, *args, **kwargs):
|
paulo@40
|
144 self._input_queue = kwargs.pop("input_queue")
|
paulo@40
|
145 self._output_queue = kwargs.pop("output_queue")
|
paulo@40
|
146 threading.Thread.__init__(self, *args, **kwargs)
|
paulo@40
|
147 self.daemon = True
|
paulo@40
|
148
|
paulo@40
|
149 def run(self):
|
paulo@40
|
150 while True:
|
paulo@40
|
151 (idx, url) = self._input_queue.get()
|
paulo@40
|
152 docfeed = None
|
paulo@40
|
153 try:
|
paulo@40
|
154 docfeed = _process_url(url)
|
paulo@40
|
155 except Exception as e:
|
paulo@42
|
156 logging.info("(%s) exception: %s" % (url, e))
|
paulo@40
|
157 self._output_queue.put((idx, docfeed))
|
paulo@40
|
158
|
paulo@40
|
159
|
paulo@44
|
160 def main(input_queue, output_queue, lock):
|
paulo@41
|
161 ret = ''
|
paulo@41
|
162
|
paulo@44
|
163 with lock:
|
paulo@44
|
164 epoch_now = time.time()
|
paulo@44
|
165 dtnow = datetime.datetime.fromtimestamp(epoch_now)
|
paulo@41
|
166
|
paulo@44
|
167 if os.path.exists(CACHE_HTML_FILE) and (epoch_now - os.stat(CACHE_HTML_FILE).st_mtime) < float(CACHE_LIFE):
|
paulo@44
|
168 with open(CACHE_HTML_FILE) as cache_html_file:
|
paulo@44
|
169 ret = cache_html_file.read()
|
paulo@41
|
170
|
paulo@44
|
171 else:
|
paulo@44
|
172 with open(FEEDS_FILE) as feeds_file:
|
paulo@44
|
173 feedlines = feeds_file.readlines()
|
paulo@41
|
174
|
paulo@44
|
175 docstruct = [None]*len(feedlines)
|
paulo@44
|
176 num_input = 0
|
paulo@44
|
177 for (i, l) in enumerate(feedlines):
|
paulo@44
|
178 if l[0] != '#':
|
paulo@44
|
179 l = l.strip()
|
paulo@44
|
180 input_queue.put((i, l))
|
paulo@44
|
181 num_input += 1
|
paulo@41
|
182
|
paulo@44
|
183 for _ in range(num_input):
|
paulo@44
|
184 (idx, docfeed) = output_queue.get()
|
paulo@44
|
185 docstruct[idx] = docfeed
|
paulo@41
|
186
|
paulo@44
|
187 ret = _to_html(dtnow, docstruct)
|
paulo@41
|
188
|
paulo@44
|
189 with open(CACHE_HTML_FILE, 'w') as cache_html_file:
|
paulo@44
|
190 cache_html_file.write(ret)
|
paulo@41
|
191
|
paulo@41
|
192 return ret
|
paulo@41
|
193
|
paulo@41
|
194
|
paulo@42
|
195 class MyRssApp:
|
paulo@42
|
196 def __init__(self):
|
paulo@42
|
197 self._iq = Queue.Queue(MAX_THREADS)
|
paulo@42
|
198 self._oq = Queue.Queue(MAX_THREADS)
|
paulo@44
|
199 self._main_lock = threading.Lock()
|
paulo@39
|
200
|
paulo@42
|
201 for _ in range(MAX_THREADS):
|
paulo@42
|
202 WorkerThread(input_queue=self._iq, output_queue=self._oq).start()
|
paulo@42
|
203
|
paulo@42
|
204 def __call__(self, environ, start_response):
|
paulo@44
|
205 response_body = main(self._iq, self._oq, self._main_lock)
|
paulo@42
|
206 response_headers = [
|
paulo@42
|
207 ("Content-Type", "text/html"),
|
paulo@42
|
208 ("Content-Length", str(len(response_body))),
|
paulo@42
|
209 ]
|
paulo@42
|
210 start_response("200 OK", response_headers)
|
paulo@42
|
211
|
paulo@42
|
212 return [response_body]
|