Changeset 254

Show
Ignore:
Timestamp:
08/25/05 02:12:20 (3 years ago)
Author:
zool
Message:

optionally pass in a model

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • bbox/bbox/__init__.py

    Revision 250 Revision 254
    1# bbox - an RSS / RDF aggregator 1# bbox - an RSS / RDF aggregator 
    2# Jo Walsh - Dec 2004 - Mar 2005 2# Jo Walsh - Dec 2004 - Mar 2005 
    3 3 
    4# This code owes heavily to the approach and source in Edd Dumbill's  4# This code owes heavily to the approach and source in Edd Dumbill's  
    5# IBM Developerworks article on aggregating RSS with contexts: 5# IBM Developerworks article on aggregating RSS with contexts: 
    6# http://www-106.ibm.com/developerworks/xml/library/x-rdfprov.html 6# http://www-106.ibm.com/developerworks/xml/library/x-rdfprov.html 
    7 7 
    8# It uses Mark Pilgrim's feedparser, at http://feedparser.org/ 8# It uses Mark Pilgrim's feedparser, at http://feedparser.org/ 
    9# This software has 2000 tests. The code is included in this package. 9# This software has 2000 tests. The code is included in this package. 
    10 10 
    11# It uses the 'rdfobj', an object interface to the python interface 11# It uses the 'rdfobj', an object interface to the python interface 
    12# to the redland rdf toolkit. This is also included. 12# to the redland rdf toolkit. This is also included. 
    13# redland is at http://www.redland.opensource.ac.uk/ 13# redland is at http://www.redland.opensource.ac.uk/ 
    14  14  
    15import feedparser 15import feedparser 
    16import time, datetime 16import time, datetime 
    17import rdfobj 17import rdfobj 
    18import RDF 18import RDF 
    19from bbox.politehttp import polite_request 19from bbox.politehttp import polite_request 
    20import bbox.spatialStore 20import bbox.spatialStore 
    21import bbox.config 21import bbox.config 
    22import os 22import os 
    23from warnings import warn 23from warnings import warn 
    24 24 
    25class BBox: 25class BBox: 
    26 26 
    27    def __init__(self,spatial=None,verbose=None,always_visit=None,db=None): 27    def __init__(self,spatial=None,verbose=None,always_visit=None,db=None,model=None): 
    28     28     
    29        """We initialise a bbox by passing a database to it. If wishing to use an optional spatial index, set spatial = [name of index or database]. setting verbose to a true value turns on BBox's stream of consciousness.""" 29        """We initialise a bbox by passing a database to it. If wishing to use an optional spatial index, set spatial = [name of index or database]. setting verbose to a true value turns on BBox's stream of consciousness.""" 
    30        if spatial is not None: 30        if spatial is not None: 
    31            self.spatialStore = bbox.spatialStore.SpatialStore(database=spatial) 31            self.spatialStore = bbox.spatialStore.SpatialStore(database=spatial) 
    32        else: 32        else: 
    33            self.spatialStore = None 33            self.spatialStore = None 
    34 34 
    35        self._verbose = verbose 35        self._verbose = verbose 
    36        self._visit_true = always_visit 36        self._visit_true = always_visit 
    37         37         
    38        os.chdir(bbox.config.store) 38        os.chdir(bbox.config.store) 
    39        if db is None: 39        if db is None: 
    40            db = bbox.config.db 40            db = bbox.config.db 
    41        self.model = rdfobj.Model(db,db='hash')  41         if model is not None: 
    42        self.model.load(bbox.config.boot)  42             self.model = model 
       43         else:     
       44             self.model = rdfobj.Model(db,db='hash') 
       45             self.model.load(bbox.config.boot) 
       46  
    43        from rdfobj import fbox 47        from rdfobj import fbox 
    44        fbox = rdfobj.fbox 48        fbox = rdfobj.fbox 
    45        counter = self.model.fetch(fbox.Visit_Count) 49        counter = self.model.fetch(fbox.Visit_Count) 
    46        c = counter[fbox.count] 50        c = counter[fbox.count] 
    47        if c is None: c = 0 51        if c is None: c = 0 
    48        c = int(str(c))+1 52        c = int(str(c))+1 
    49        counter[fbox.count] = str(c) 53        counter[fbox.count] = str(c) 
    50         54         
    51        if counter is None: 55        if counter is None: 
    52            v = self.model.create(fbox.Visit_Count, uri=fbox.Visit_Count) 56            v = self.model.create(fbox.Visit_Count, uri=fbox.Visit_Count) 
    53            v[fbox.count] = 0 57            v[fbox.count] = 0 
    54             58             
    55 59 
    56    def mention(self,thought): 60    def mention(self,thought): 
    57        """If BBox is constructed with verbose=1, prints to STDOUT (currently) a record of what it's up to.""" 61        """If BBox is constructed with verbose=1, prints to STDOUT (currently) a record of what it's up to.""" 
    58        if self._verbose: 62        if self._verbose: 
    59            print(thought) 63            print(thought) 
    60             64             
    61    def read_subscriptions(self): 65    def read_subscriptions(self): 
    62        """read_subscriptions() picks up the latest RSS feed updates. """ 66        """read_subscriptions() picks up the latest RSS feed updates. """ 
    63        self.mention("checking subscriptions.")  67        self.mention("checking subscriptions.")  
    64        subs = self.subscriptions() 68        subs = self.subscriptions() 
    65        from rdfobj import fbox 69        from rdfobj import fbox 
    66        fbox = rdfobj.fbox 70        fbox = rdfobj.fbox 
    67        for s in subs: 71        for s in subs: 
    68            self.mention("reading "+str(s[fbox.channel])) 72            self.mention("reading "+str(s[fbox.channel])) 
    69            format = s[fbox.format].uri() 73            format = s[fbox.format].uri() 
    70            c = s[fbox.channel] 74            c = s[fbox.channel] 
    71 75 
    72            # see if we're actually due a visit 76            # see if we're actually due a visit 
    73            due = self.visit_scheduled(s) 77            due = self.visit_scheduled(s) 
    74            if due is None:  78            if due is None:  
    75                print "nothing due to look at!" 79                print "nothing due to look at!" 
    76                subs.next() 80                subs.next() 
    77            else: 81            else: 
    78                if format == fbox.rss: 82                if format == fbox.rss: 
    79                    self.read_rss(c.uri(),subscription=s) 83                    self.read_rss(c.uri(),subscription=s) 
    80                elif format == fbox.rdf: 84                elif format == fbox.rdf: 
    81                    self.read_rdf(s[fbox.channel].uri(),subscription=s) 85                    self.read_rdf(s[fbox.channel].uri(),subscription=s) 
    82             86             
    83    def read_rss(self,uri,context=None,subscription=None,xml=None): 87    def read_rss(self,uri,context=None,subscription=None,xml=None): 
    84        """Read updates from an RSS feed.""" 88        """Read updates from an RSS feed.""" 
    85 89 
    86        #if subscription is None: subscription = {} 90        #if subscription is None: subscription = {} 
    87        rss = rdfobj.rss 91        rss = rdfobj.rss 
    88        dc = rdfobj.dc 92        dc = rdfobj.dc 
    89        ical = rdfobj.ical 93        ical = rdfobj.ical 
    90        fbox = rdfobj.fbox 94        fbox = rdfobj.fbox 
    91        geo = rdfobj.geo 95        geo = rdfobj.geo 
    92         96         
    93        result = self.politely_get_uri(uri,subscription=subscription)  97        result = self.politely_get_uri(uri,subscription=subscription)  
    94 98 
    95        channel = self.model.fetch(uri)          99        channel = self.model.fetch(uri)          
    96        """If we got a feed object back from the request, then create a 100        """If we got a feed object back from the request, then create a 
    97        context for this visit to the feed, and store the entries that we 101        context for this visit to the feed, and store the entries that we 
    98        collected from it.""" 102        collected from it.""" 
    99        if self._visit_true: 103        if self._visit_true: 
    100            pass  104            pass  
    101        elif result['status'] != 200:  105        elif result['status'] != 200:  
    102            return 106            return 
    103        items = [] 107        items = [] 
    104        feed = feedparser.parse(result['data']) 108        feed = feedparser.parse(result['data']) 
    105        if feed.has_key('feed'): 109        if feed.has_key('feed'): 
    106            context = self.visit(uri) 110            context = self.visit(uri) 
    107            # existence of exact duplicates? 111            # existence of exact duplicates? 
    108             112             
    109            for e in feed.entries: 113            for e in feed.entries: 
    110                link = str(e.link) 114                link = str(e.link) 
    111                title = None 115                title = None 
    112                item = self.model.create( rss.item, uri=link, context = context ) 116                item = self.model.create( rss.item, uri=link, context = context ) 
    113                 117                 
    114                if e.has_key('summary'): item[rss.description] = str(e.summary) 118                if e.has_key('summary'): item[rss.description] = str(e.summary) 
    115 119 
    116                if e.has_key('content'): item[rss.description] = str(e.content) 120                if e.has_key('content'): item[rss.description] = str(e.content) 
    117 121 
    118                if e.has_key('title'): 122                if e.has_key('title'): 
    119                    item[rss.title] = str(e.title) 123                    item[rss.title] = str(e.title) 
    120                    title = str(e.title) 124                    title = str(e.title) 
    121                     125                     
    122                item[fbox.channel] = channel 126                item[fbox.channel] = channel 
    123                 127                 
    124                # d.entries[0].modified_parsed is common 128                # d.entries[0].modified_parsed is common 
    125                 129                 
    126                time_tuple = None 130                time_tuple = None 
    127                if e.has_key('modified_parsed'): 131                if e.has_key('modified_parsed'): 
    128                    time_tuple = e.modified_parsed 132                    time_tuple = e.modified_parsed 
    129                elif e.has_key('created_parsed'): 133                elif e.has_key('created_parsed'): 
    130                    time_tuple = e.created_parsed 134                    time_tuple = e.created_parsed 
    131                 135                 
    132                # item[ical.datetime] = some process with time_tuple and strftime 136                # item[ical.datetime] = some process with time_tuple and strftime 
    133                # d = datetime.datetime(time_tuple) 137                # d = datetime.datetime(time_tuple) 
    134                # ical_date = ical_datetime.datetime_to_string(d) 138                # ical_date = ical_datetime.datetime_to_string(d) 
    135                # print ical_date 139                # print ical_date 
    136                # item[ical.datetime] = ical_date 140                # item[ical.datetime] = ical_date 
    137                # not much use without a timestamp 141                # not much use without a timestamp 
    138         142         
    139                if time_tuple is None: 143                if time_tuple is None: 
    140                    continue 144                    continue 
    141                ical_enough = time.strftime("%Y%m%dT%H%M%SZ",time_tuple) 145                ical_enough = time.strftime("%Y%m%dT%H%M%SZ",time_tuple) 
    142                item[ical.datetime] = ical_enough 146                item[ical.datetime] = ical_enough 
    143 147 
    144                rdf_type = None 148                rdf_type = None 
    145                if e.has_key('rdf_type'): 149                if e.has_key('rdf_type'): 
    146                    rdf_type = str(e['rdf_type']) 150                    rdf_type = str(e['rdf_type']) 
    147                    item[rdf.type] = rdf_type 151                    item[rdf.type] = rdf_type 
    148 152 
    149                if e.has_key('geo_lat'): print "LATT!!! "+str(e['geo_lat']) 153                if e.has_key('geo_lat'): print "LATT!!! "+str(e['geo_lat']) 
    150                if e.has_key('geo_lat') and e.has_key('geo_long'): 154                if e.has_key('geo_lat') and e.has_key('geo_long'): 
    151                    lat = str(e['geo_lat']) 155                    lat = str(e['geo_lat']) 
    152                    long = str(e['geo_long']) 156                    long = str(e['geo_long']) 
    153                    item[geo.lat] = lat 157                    item[geo.lat] = lat 
    154                    item[geo.long] = long 158                    item[geo.long] = long 
    155                     159                     
    156                    """Update the spatial index, if we have one.""" 160                    """Update the spatial index, if we have one.""" 
    157                    if self.spatialStore is not None: 161                    if self.spatialStore is not None: 
    158                        self.spatialStore.add_or_update_geom(rdf_type=rdf_type,name=title,x=long,y=lat,uri=link) 162                        self.spatialStore.add_or_update_geom(rdf_type=rdf_type,name=title,x=long,y=lat,uri=link) 
    159                items.append(item) 163                items.append(item) 
    160        return items 164        return items 
    161                             165                             
    162    def read_rdf(self,uri,subscription=None,xml=None):   166    def read_rdf(self,uri,subscription=None,xml=None):   
    163        """Read updates from an RDF url.""" 167        """Read updates from an RDF url.""" 
    164        from rdfobj import geo, dc, rdf 168        from rdfobj import geo, dc, rdf 
    165        geo = rdfobj.geo 169        geo = rdfobj.geo 
    166        dc = rdfobj.dc 170        dc = rdfobj.dc 
    167        rdf = rdfobj.rdf 171        rdf = rdfobj.rdf 
    168         172         
    169        result = self.politely_get_uri(uri,subscription=subscription) 173        result = self.politely_get_uri(uri,subscription=subscription) 
    170        if self._visit_true: 174        if self._visit_true: 
    171            pass 175            pass 
    172        elif result['status'] != 200: 176        elif result['status'] != 200: 
    173            return 177            return 
    174         178         
    175        context = self.visit(uri) 179        context = self.visit(uri) 
    176         180         
    177        # we can't just use load() because we want the visit context, and to search for spatial things and index them while we're parsing... 181        # we can't just use load() because we want the visit context, and to search for spatial things and index them while we're parsing... 
    178        lats = {} 182        lats = {} 
    179        longs = {} 183        longs = {} 
    180        titles = {} 184        titles = {} 
    181        types = {} 185        types = {} 
    182        parser = RDF.Parser('raptor') 186        parser = RDF.Parser('raptor') 
    183        stream = parser.parse_as_stream(RDF.Uri(uri)) 187        stream = parser.parse_as_stream(RDF.Uri(uri)) 
    184        subjects = {}    188        subjects = {}    
    185        if stream: 189        if stream: 
    186            while not stream.end(): 190            while not stream.end(): 
    187                statement = stream.current() 191                statement = stream.current() 
    188                subjects[statement.subject] = 1 192                subjects[statement.subject] = 1 
    189                self.model.model.add_statement(statement,context) 193                self.model.model.add_statement(statement,context) 
    190                # pls don't blame me, i just want to get something working fast 194                # pls don't blame me, i just want to get something working fast 
    191                if self.spatialStore is not None: 195                if self.spatialStore is not None: 
    192                    if statement.predicate == RDF.Node(uri_string=str(geo.lat)): 196                    if statement.predicate == RDF.Node(uri_string=str(geo.lat)): 
    193                        lats[str(statement.subject)] = str(statement.object) 197                        lats[str(statement.subject)] = str(statement.object) 
    194                         198                         
    195                    elif statement.predicate == RDF.Node(uri_string=str(geo.long)): 199                    elif statement.predicate == RDF.Node(uri_string=str(geo.long)): 
    196                        longs[str(statement.subject)] = str(statement.object) 200                        longs[str(statement.subject)] = str(statement.object) 
    197         201         
    198                    elif statement.predicate == RDF.Node(uri_string=str(dc.title)): 202                    elif statement.predicate == RDF.Node(uri_string=str(dc.title)): 
    199                        titles[str(statement.subject)] = str(statement.object) 203                        titles[str(statement.subject)] = str(statement.object) 
    200                         204                         
    201                    elif statement.predicate == RDF.Node(uri_string=str(rdf.type)): 205                    elif statement.predicate == RDF.Node(uri_string=str(rdf.type)): 
    202                        types[str(statement.subject)] = str(statement.object) 206                        types[str(statement.subject)] = str(statement.object) 
    203                         207                         
    204                stream.next() 208                stream.next() 
    205        objects = [] 209        objects = [] 
    206        for s in subjects.keys(): 210        for s in subjects.keys(): 
    207            objects.append(self.model.fetch(s)) 211            objects.append(self.model.fetch(s)) 
    208             212             
    209        if self.spatialStore is not None: 213        if self.spatialStore is not None: 
    210            for k in lats.keys(): 214            for k in lats.keys(): 
    211                lat = lats[k] 215                lat = lats[k] 
    212                long = longs[k] 216                long = longs[k] 
    213                title = None 217                title = None 
    214                type = None 218                type = None 
    215                if titles.has_key(k): 219                if titles.has_key(k): 
    216                    title = titles[k] 220                    title = titles[k] 
    217                if types.has_key(k): 221                if types.has_key(k): 
    218                    type = types[k] 222                    type = types[k] 
    219                warn("updating "+k) 223                warn("updating "+k) 
    220                self.spatialStore.add_or_update_geom(uri=k,rdf_type=type,name=title,x=long,y=lat) 224                self.spatialStore.add_or_update_geom(uri=k,rdf_type=type,name=title,x=long,y=lat) 
    221        return objects 225        return objects 
    222         226         
    223    def politely_get_uri(self,uri,subscription=None): 227    def politely_get_uri(self,uri,subscription=None): 
    224        """Request a copy of the document at a url, first checking that it has changed since what we record as last-modified and the last etag that we have for it.""" 228        """Request a copy of the document at a url, first checking that it has changed since what we record as last-modified and the last etag that we have for it.""" 
    225         229         
    226        # we should deal with etag/last-mod politely here too @@TODO 230        # we should deal with etag/last-mod politely here too @@TODO 
    227        #visit = self.visit(uri) 231        #visit = self.visit(uri) 
    228        result = None 232        result = None 
    229        fbox = rdfobj.fbox 233        fbox = rdfobj.fbox 
    230 234 
    231        if subscription is None: 235        if subscription is None: 
    232            # we might just be using the parser without the context management 236            # we might just be using the parser without the context management 
    233            result = polite_request(str(uri)) 237            result = polite_request(str(uri)) 
    234            subscription = {'fake':1} 238            subscription = {'fake':1} 
    235             239             
    236        elif self._visit_true is not None: 240        elif self._visit_true is not None: 
    237            # we might always want to read the feed content (for debugging reasons) 241            # we might always want to read the feed content (for debugging reasons) 
    238            result = polite_request(str(uri))       242            result = polite_request(str(uri))       
    239             243             
    240        elif subscription[fbox.last_etag] is not None: 244        elif subscription[fbox.last_etag] is not None: 
    241            result = polite_request(str(uri),etag=str(subscription[fbox.last_etag])) 245            result = polite_request(str(uri),etag=str(subscription[fbox.last_etag])) 
    242        elif subscription[fbox.last_modified] is not None: 246        elif subscription[fbox.last_modified] is not None: 
    243            result = polite_request(str(uri),last_modified=str(subscription[fbox.last_modified])) 247            result = polite_request(str(uri),last_modified=str(subscription[fbox.last_modified])) 
    244        else: result = polite_request(str(uri)) 248        else: result = polite_request(str(uri)) 
    245        if result is None: 249        if result is None: 
    246            result = {'status':404} 250            result = {'status':404} 
    247            return result 251            return result 
    248 252 
    249        if result.has_key('status'): 253        if result.has_key('status'): 
    250            # this was a HTTP request 254            # this was a HTTP request 
    251            self.mention("received response: "+str(result['status'])) 255            self.mention("received response: "+str(result['status'])) 
    252            256            
    253            """Take actions about other kinds of HTTP statuses.(TODO)""" 257            """Take actions about other kinds of HTTP statuses.(TODO)""" 
    254            # handling different HTTP statuses. 258            # handling different HTTP statuses. 
    255            if subscription.has_key('fake') is None: 259            if subscription.has_key('fake') is None: 
    256                warn(type(subscription)) 260                warn(type(subscription)) 
    257                subscription[fbox.http_status] = str(result['status']) 261                subscription[fbox.http_status] = str(result['status']) 
    258                subscription[fbox.last_etag] = result['etag'] 262                subscription[fbox.last_etag] = result['etag'] 
    259                subscription[fbox.last_modified] = result['lastmodified']        263                subscription[fbox.last_modified] = result['lastmodified']        
    260                subscription[fbox.last_visited] = time.strftime("%Y%m%dT%H%M%SZ")  264                subscription[fbox.last_visited] = time.strftime("%Y%m%dT%H%M%SZ")  
    261         265         
    262        # a 'file:/' uri will only have result['data'] 266        # a 'file:/' uri will only have result['data'] 
    263        elif result['data'] is not None: 267        elif result['data'] is not None: 
    264            # pretend we have a positive HTTP status 268            # pretend we have a positive HTTP status 
    265            result['status'] = 200 269            result['status'] = 200 
    266 270 
    267        return result 271        return result 
    268 272 
    269    def subscriptions(self): 273    def subscriptions(self): 
    270        """Returns a list (Iterator type) of the URLs at which 274        """Returns a list (Iterator type) of the URLs at which 
    271        there is a feed that we are subscribed to (fbox:Feed type)""" 275        there is a feed that we are subscribed to (fbox:Feed type)""" 
    272        from rdfobj import fbox, rdf 276        from rdfobj import fbox, rdf 
    273        fbox = rdfobj.fbox 277        fbox = rdfobj.fbox 
    274        rdf = rdfobj.rdf 278        rdf = rdfobj.rdf 
    275        subs = self.model.search(rdf.type,fbox.Feed) 279        subs = self.model.search(rdf.type,fbox.Feed) 
    276        return subs 280        return subs 
    277 281 
    278    def subscription(self,uri): 282    def subscription(self,uri): 
    279        """Given a uri, returns the rdfobj which is the subscription it represents.""" 283        """Given a uri, returns the rdfobj which is the subscription it represents.""" 
    280        obj = self.model.fetch(uri) 284        obj = self.model.fetch(uri) 
    281        return obj 285        return obj 
    282 286 
    283    def items(self,uri,since=None,until=None): 287    def items(self,uri,since=None,until=None): 
    284        """Get items from a feed, optionally filtering by date. (not completely implemented)""" 288        """Get items from a feed, optionally filtering by date. (not completely implemented)""" 
    285        from rdfobj import fbox, dc, rss 289        from rdfobj import fbox, dc, rss 
    286        rss = fbox.rss 290        rss = fbox.rss 
    287        dc = fbox.dc 291        dc = fbox.dc 
    288        fbox = rdfobj.fbox 292        fbox = rdfobj.fbox 
    289        s = self.subscription(uri) 293        s = self.subscription(uri) 
    290        c = s[fbox.channel] 294        c = s[fbox.channel] 
    291        out = []  295        out = []  
    292        if since is not None: 296        if since is not None: 
    293            for i in c[rss.items]: 297            for i in c[rss.items]: 
    294                warn(i[dc.date]) 298                warn(i[dc.date]) 
    295                if i[dc.date] > since: 299                if i[dc.date] > since: 
    296                    out.append(i) 300                    out.append(i) 
    297 301 
    298        return c.rss_items 302        return c.rss_items 
    299   303   
    300    def subscribe(self,feed=None,format=None,interval=None): 304    def subscribe(self,feed=None,format=None,interval=None): 
    301        """subscribe() creates a subscription to a uri. format is either 'rss' or 'rdf'. RDF is assumed if none is specified. Interval is the maximum interval in minutes that a feed should be checked at. It sends polite HTTP requests so don't worry about setting it to a bit more often than you might need. A value in minutes - defaults to 100 minutes.""" 305        """subscribe() creates a subscription to a uri. format is either 'rss' or 'rdf'. RDF is assumed if none is specified. Interval is the maximum interval in minutes that a feed should be checked at. It sends polite HTTP requests so don't worry about setting it to a bit more often than you might need. A value in minutes - defaults to 100 minutes.""" 
    302        from rdfobj import fbox 306        from rdfobj import fbox 
    303        fbox = rdfobj.fbox       307        fbox = rdfobj.fbox       
    304        if feed is None: return 308        if feed is None: return 
    305 309 
    306        f = self.model.search(fbox.channel,feed) 310        f = self.model.search(fbox.channel,feed) 
    307        found = None 311        found = None 
    308        for n in f: 312        for n in f: 
    309            found = 1     313            found = 1     
    310        if found is not None: 314        if found is not None: 
    311            return 315            return 
    312 316 
    313        self.mention("subscribing to "+str(feed)) 317        self.mention("subscribing to "+str(feed)) 
    314 318 
    315        if format is None:  319        if format is None:  
    316            format = fbox.rdf 320            format = fbox.rdf 
    317        elif format == 'rss': 321        elif format == 'rss': 
    318            format = fbox.rss 322            format = fbox.rss 
    319        elif format == 'rdf': 323        elif format == 'rdf': 
    320            format = fbox.rdf 324            format = fbox.rdf 
    321 325 
    322        if interval is None: interval = str(100) 326        if interval is None: interval = str(100) 
    323 327 
    324        ff = self.model.create( fbox.Feed, uri=None ) 328        ff = self.model.create( fbox.Feed, uri=None ) 
    325        ff[fbox.channel] = str(feed) 329        ff[fbox.channel] = str(feed) 
    326        ff[fbox.format] = str(format) 330        ff[fbox.format] = str(format) 
    327        ff[fbox.interval] = interval 331        ff[fbox.interval] = interval 
    328 332 
    329        return ff 333        return ff 
    330 334 
    331    def update(self): 335    def update(self): 
    332        """Causes all the subscribed URLs to be visited for updates.""" 336        """Causes all the subscribed URLs to be visited for updates.""" 
    333        subs = self.subscriptions() 337        subs = self.subscriptions() 
    334        from rdfobj import fbox 338        from rdfobj import fbox 
    335        fbox = rdfobj.fbox 339        fbox = rdfobj.fbox 
    336        while not subs.end(): 340        while not subs.end(): 
    337            s = subs.current() 341            s = subs.current() 
    338            self.visit(s[fbox.channel]) 342            self.visit(s[fbox.channel]) 
    339            subs.next() 343            subs.next() 
    340 344 
    341    def visit(self,uri=None): 345    def visit(self,uri=None): 
    342        """Creates an anonymous object which records a visit that we 346        """Creates an anonymous object which records a visit that we 
    343        paid to a feed, including a counter of times visited. This object is 347        paid to a feed, including a counter of times visited. This object is 
    344        used as a Redland context for all the information collected from a feed 348        used as a Redland context for all the information collected from a feed 
    345        during this visit.""" 349        during this visit.""" 
    346        # redland had problems serialising models with bnode context uris  350        # redland had problems serialising models with bnode context uris  
    347        count = self.counter() 351        count = self.counter() 
    348        from rdfobj import fbox 352        from rdfobj import fbox 
    349        fbox = rdfobj.fbox 353        fbox = rdfobj.fbox 
    350        visit_uri = str(fbox.visit)+'/'+str(count) 354        visit_uri = str(fbox.visit)+'/'+str(count) 
    351        visit = self.model.create( fbox.Visit , visit_uri) 355        visit = self.model.create( fbox.Visit , visit_uri) 
    352 356 
    353        visit[fbox.source] = uri 357        visit[fbox.source] = uri 
    354        t = time.strftime("%Y%m%dT%H%M%SZ") 358        t = time.strftime("%Y%m%dT%H%M%SZ") 
    355        visit[fbox.timestamp] = t 359        visit[fbox.timestamp] = t 
    356        return RDF.Node(RDF.Uri(str(visit.uri()))) 360        return RDF.Node(RDF.Uri(str(visit.uri()))) 
    357 361 
    358    def user(self,token=None,nick=None,mbox=None): 362    def user(self,token=None,nick=None,mbox=None): 
    359        """Passed either a user's login token, mbox and name, resolved to mutual exclusion in that order, and returns any corresponding user / foaf:Person object. No security - handle this yourself elsewhere!""" 363        """Passed either a user's login token, mbox and name, resolved to mutual exclusion in that order, and returns any corresponding user / foaf:Person object. No security - handle this yourself elsewhere!""" 
    360        from rdfobj import foaf 364        from rdfobj import foaf 
    361        foaf = rdfobj.foaf 365        foaf = rdfobj.foaf 
    362        if token is not None: 366        if token is not None: 
    363            users = self.model.search(foaf.auth_token,token) 367            users = self.model.search(foaf.auth_token,token) 
    364            for u in users: 368            for u in users: 
    365                return u[foaf.alias] 369                return u[foaf.alias] 
    366        if mbox is not None: 370        if mbox is not None: 
    367            users = self.model.search(foaf.mbox,mbox) 371            users = self.model.search(foaf.mbox,mbox) 
    368            for u in users: 372            for u in users: 
    369                return u 373                return u 
    370        if nick is not None: 374        if nick is not None: 
    371            o = [] 375            o = [] 
    372            users = self.model.search(foaf.name,nick) 376            users = self.model.search(foaf.name,nick) 
    373            for u in users: 377            for u in users: 
    374                o.append(u) 378                o.append(u) 
    375            users = self.model.search(foaf.givenName,nick) 379            users = self.model.search(foaf.givenName,nick) 
    376            for u in users: o.append(u) 380            for u in users: o.append(u) 
    377            return o 381            return o 
    378 382 
    379    def add_user(self,nick=None,mbox=None,password=None): 383    def add_user(self,nick=None,mbox=None,password=None): 
    380        """ Create a new user foaf:Person""" 384        """ Create a new user foaf:Person""" 
    381        store = self.store 385        store = self.store 
    382        from rdfobj import foaf, wlan 386        from rdfobj import foaf, wlan 
    383        foaf = rdfobj.foaf 387        foaf = rdfobj.foaf 
    384        wlan = rdfobj.wlan 388        wlan = rdfobj.wlan 
    385 389 
    386        obj = self.model.create(foaf.Person,uri=uri) 390        obj = self.model.create(foaf.Person,uri=uri) 
    387        obj[foaf.mbox] = mbox 391        obj[foaf.mbox] = mbox 
    388        obj[foaf.nick] = nick 392        obj[foaf.nick] = nick 
    389        if page is not None: obj[foaf.homepage] = page 393        if page is not None: obj[foaf.homepage] = page 
    390        self.obj = obj 394        self.obj = obj 
    391 395 
    392        """ Create a kind of shadow user where we store the password and the logged-in token, so they won't get serialised accidentally along with the user. """ 396        """ Create a kind of shadow user where we store the password and the logged-in token, so they won't get serialised accidentally along with the user. """ 
    393 397 
    394        auth = store.create(foaf.AuthedPerson) 398        auth = store.create(foaf.AuthedPerson) 
    395        auth[foaf.password] = password 399        auth[foaf.password] = password 
    396        auth[foaf.nick] = nick 400        auth[foaf.nick] = nick 
    397        auth[foaf.alias] = obj 401        auth[foaf.alias] = obj 
    398 402 
    399        token = self.auth_token() 403        token = self.auth_token() 
    400        auth[foaf.auth_token] = token 404        auth[foaf.auth_token] = token 
    401        self.model.sync() 405        self.model.sync() 
    402        return token 406        return token 
    403 407 
    404    def auth_token(self): 408    def auth_token(self): 
    405        """Generate a random auth token.""" 409        """Generate a random auth token.""" 
    406        x = '' 410        x = '' 
    407        for  n in range(0, 6): 411        for  n in range(0, 6): 
    408            x = x + chr(65 + random.randint(0, 26)) 412            x = x + chr(65 + random.randint(0, 26)) 
    409        return x         413        return x         
    410         414         
    411    def visit_scheduled(self,sub): 415    def visit_scheduled(self,sub): 
    412        """Compare the last visited time, if that's applicable, to the interval between events (rather than a schedule? perhaps we'll have to re-think this later."""    416        """Compare the last visited time, if that's applicable, to the interval between events (rather than a schedule? perhaps we'll have to re-think this later."""    
    413        if self._visit_true is not None: 417        if self._visit_true is not None: 
    414            return 1 418            return 1 
    415        from rdfobj import fbox 419        from rdfobj import fbox 
    416        fbox = rdfobj.fbox 420        fbox = rdfobj.fbox 
    417        last = sub[fbox.last_visited] 421        last = sub[fbox.last_visited] 
    418        if last is None: 422        if last is None: 
    419            return 1 423            return 1 
    420        t = time.time() 424        t = time.time() 
    421        # convert last time simply from ical to epoch? 425        # convert last time simply from ical to epoch? 
    422 426 
    423        return 1 427        return 1 
    424        since = t - float(str(last)) 428        since = t - float(str(last)) 
    425         429         
    426        interval = sub[fbox.interval] 430        interval = sub[fbox.interval] 
    427        if interval is None: 431        if interval is None: 
    428            sub[fbox.interval] = str(100) 432            sub[fbox.interval] = str(100) 
    429            interval = sub[fbox.interval] 433            interval = sub[fbox.interval] 
    430        secs = int(str(interval))*60 434        secs = int(str(interval))*60 
    431        if since >= secs: 435        if since >= secs: 
    432            return 1 436            return 1 
    433        return None 437        return None 
    434                 438                 
    435    def counter(self): 439    def counter(self): 
    436        """Update the counter that's used to generate visit context URIs.""" 440        """Update the counter that's used to generate visit context URIs.""" 
    437        from rdfobj import fbox 441        from rdfobj import fbox 
    438        fbox = rdfobj.fbox 442        fbox = rdfobj.fbox 
    439        counter = self.model.fetch(fbox.Visit_Count) 443        counter = self.model.fetch(fbox.Visit_Count) 
    440        c = counter[fbox.count] 444        c = counter[fbox.count] 
    441        c = int(str(c))+1 445        c = int(str(c))+1 
    442        counter[fbox.count] = str(c) 446        counter[fbox.count] = str(c) 
    443        return c 447        return c 
    444 448 
    445 449 
    446if __name__ == "__main__": 450if __name__ == "__main__": 
    447    bbox = BBox(visit_true = 1) 451    bbox = BBox(visit_true = 1) 
    448    bbox.subscribe(feed='http://frot.org/wirelesslondon/bbox.rdf',format=fbox.rss) 452    bbox.subscribe(feed='http://frot.org/wirelesslondon/bbox.rdf',format=fbox.rss) 
    449    bbox.subscribe(feed='http://frot.org/devlog/index.rss',format=fbox.rss) 453    bbox.subscribe(feed='http://frot.org/devlog/index.rss',format=fbox.rss) 
    450    bbox.subscribe(feed='http://zooleika.org.uk/bio/foaf.rdf',format=fbox.rdf) 454    bbox.subscribe(feed='http://zooleika.org.uk/bio/foaf.rdf',format=fbox.rdf) 
    451    bbox.read_subscriptions() 455    bbox.read_subscriptions()