Edgewall Software

MacroBazaar: RSSget.py

File RSSget.py, 6.0 KB (added by graeme@…, 6 years ago)

an rss feed reader

Line 
1"""Example macro."""
2import urllib
3import time
4import string
5import os
6import httplib
7import urllib2
8import StringIO
9from xml.dom import minidom
10from trac.util import escape
11
12## Usage: [[RSSget(http://www.example.com/feed.xml)]]
13##
14
15
16CACHE_DIR = "/tmp";
17CACHE_ID = "tracrss"
18CACHE_INTERVAL = 1500
19News = "" # output
20RESULTS_FULL = 1;
21RESULTS_TOTAL = 5;
22
23
24# Flow
25# 1. check cache
26# 2. if there is a hit, make sure its fresh
27# 3. if cached obj fails freshness check, fetch remote
28# 4. if remote fails, return stale object, or error
29
30## PART ONE: check the cache
31def cache_lookup(url):
32        # generate a positive hash of the url
33        cache_id = abs(hash(url))
34        # print cache_id
35        # look through the cache dir for matches
36        cached_files = os.listdir(CACHE_DIR + "/" + CACHE_ID)
37        for filename in cached_files:
38                # print cached_files
39                cached_file = string.split(filename, sep=CACHE_ID)[0]
40                #check the hash
41                if (cache_id == int(cached_file)):
42                        return filename
43
44
45## PART TWO: check for freshness
46def freshness_check(filename, interval):
47        olddate = string.split(filename, sep=CACHE_ID)[1]
48        time_elapsed = time.time() - float(olddate)
49        if (time_elapsed > interval):
50                return False
51        else:
52                return True
53
54## PART THREE: if the cached one fails the freshness test
55## make a new filename for the new get.
56
57def create_filename(url):
58        # creates a uniqe but parseable filename for the cachefile
59        # consising of:
60        #       hash of url
61        #       cache id
62        #       timestamp of creation
63        # you can get the url hash and timestamp back with:
64        #       struct = string.split(filename, sep=CACHE_ID)
65        urlhash = str(abs(hash(url)));
66        now = time.time()
67        filename = urlhash + CACHE_ID + str(now);
68        return filename
69       
70## burn the old one
71def remove_old_cache(url):
72        filename = cache_lookup(url)
73        print filename
74        os.remove(CACHE_DIR + "/" + CACHE_ID + "/" + filename)
75
76## hell, burn all of 'em
77def clear_cache(url):
78        # generate a positive hash of the url
79        cache_id = abs(hash(url))
80        # print cache_id
81        # look through the cache dir for matches
82        cached_files = os.listdir(CACHE_DIR + "/" + CACHE_ID)
83        for filename in cached_files:
84                # print cached_files
85                cached_file = string.split(filename, sep=CACHE_ID)[0]
86                #check the hash
87                if (cache_id == int(cached_file)):
88                        os.remove(CACHE_DIR + "/" + CACHE_ID + "/" + filename)
89
90# a useful tool
91def _mkdir(newdir):
92    """works the way a good mkdir should :)
93        - already exists, silently complete
94        - regular file in the way, raise an exception
95        - parent directory(ies) does not exist, make them as well
96    """
97    if os.path.isdir(newdir):
98        pass
99    elif os.path.isfile(newdir):
100        raise OSError("a file with the same name as the desired " \
101                      "dir, '%s', already exists." % newdir)
102    else:
103        head, tail = os.path.split(newdir)
104        if head and not os.path.isdir(head):
105            _mkdir(head)
106        #print "_mkdir %s" % repr(newdir)
107        if tail:
108            os.mkdir(newdir)
109
110#just make sure it's there, every time..
111
112_mkdir(CACHE_DIR + "/" + CACHE_ID)
113
114       
115## write stuff to a new cache
116
117def write_to_cache(url, contents):
118        clear_cache(url)
119        #generate the filename
120        filename = create_filename(url)
121        #make sure the directory's there
122        _mkdir(CACHE_DIR + CACHE_ID)
123        #open the right file
124        cachefile = open(CACHE_DIR + "/" + CACHE_ID + "/" + filename, 'w')
125        #cram in the contents
126        cachefile.write(contents)
127        #shoehorn it back closed
128        cachefile.close
129
130
131# the actual xml parser
132
133def remote_url_get(url):
134        request = urllib2.Request(url) 
135        request.add_header('User-Agent', 'TracRSS')
136        opener = urllib2.build_opener()                                   
137        feeddata = opener.open(request).read()
138        write_to_cache(url, feeddata)
139        return feeddata
140
141def local_file_read(filename):
142        cachefile = open(CACHE_DIR + "/" + CACHE_ID + "/" + filename, 'r')
143        contents = cachefile.read()
144        return contents
145
146#parse into xml
147def parse_file(feeddata):
148       
149        rss_snippets = ""
150        titles = []
151        links = []
152        words = []
153               
154        xmlstring = StringIO.StringIO(feeddata)
155        # print str(xmlstring)
156        xmldoc = minidom.parse(xmlstring)
157        Itemlist = xmldoc.getElementsByTagName('item')
158       
159        for Item in Itemlist:
160                for node2 in Item.getElementsByTagName("title"):
161                        title = node2.firstChild.data
162                        titles.append(title)
163                for node2 in Item.getElementsByTagName("link"):
164                        link = node2.firstChild.data
165                        links.append(link)
166                for node2 in Item.getElementsByTagName("description"):
167                        description = node2.firstChild.data
168                        words.append(description)
169         
170        rss_snippets += "<dl>\n"
171        for i in range(RESULTS_FULL):
172                rss_snippets += "<dt><a href='" + links[i] + "'>" + titles[i] + "</a></dt>\n"
173                rss_snippets += "<dd><p>" + words[i] + "</p></dd>\n"
174       
175        for i in range(RESULTS_FULL, RESULTS_TOTAL):
176                rss_snippets += "<dt><a href='" + links[i] + "'>" + titles[i] + "</a></dt>\n"
177        rss_snippets += "</dl>\n"
178       
179        return rss_snippets
180       
181
182# the master logic.
183
184def rss_get_url(url):
185        # 1. check cache
186        cache_file = cache_lookup(url)
187        print cache_file
188        if cache_file:
189                print "checking freshness"
190                cache_freshtest = freshness_check(cache_file, CACHE_INTERVAL)
191                print cache_freshtest
192        # 2. if there is a hit, make sure its fresh
193                if cache_freshtest:
194                        print "file is fresh"
195                        cache_contents = local_file_read(cache_file)
196                        parsed_rss = parse_file(cache_contents)
197                        print parsed_rss
198                        return parsed_rss
199        # 3. if cached obj fails freshness check, fetch remote
200                else:
201                        print "file is stale, getting remote"
202                        remote_rss = remote_url_get(url)
203                        parsed_rss = parse_file(remote_rss)
204                        return parsed_rss
205        else:
206                print "there is no cache file, getting remote"
207                remote_rss = remote_url_get(url)
208                parsed_rss = parse_file(remote_rss)
209                return parsed_rss
210               
211        # 4. if remote fails, return stale object, or error
212        # not implemented
213       
214print rss_get_url('http://sxip.org/blog/?feed=rss2')
215
216def execute(hdf, txt, env):
217    News = rss_get_url(txt)
218        # Currently hdf is set only when the macro is called
219    # From a wiki page
220    if hdf:
221        hdf['wiki.macro.greeting'] = 'Hello World'
222       
223    # args will be null if the macro is called without parenthesis.
224    args = txt or 'No arguments'
225
226    return News
227
228
229
230