Changeset 103

Show
Ignore:
Timestamp:
06/12/05 17:39:59 (4 years ago)
Author:
zool
Message:

some older local tweaks to bbox - largely documentation fixes - plus the getitems interface

Files:

Legend:

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

    Revision 52 Revision 103
    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 politehttp import polite_request 19from politehttp import polite_request 
    20import bbox.config 20import bbox.config 
    21import os 21import os 
      22from warnings import warn 
    22os.chdir(bbox.config.store) 23os.chdir(bbox.config.store) 
    23 24 
    24class BBox: 25class BBox: 
    25 26 
    26    def __init__(self,spatialStore=None,verbose=None,always_visit=None): 27    def __init__(self,spatialStore=None,verbose=None,always_visit=None): 
    27        """We initialise a bbox by passing a database to it. If wishing to use an optional spatial index, a spatialStore object must be supplied. setting verbose to a true value turns on BBox's stream of consciousness.""" 28        """We initialise a bbox by passing a database to it. If wishing to use an optional spatial index, a spatialStore object must be supplied. setting verbose to a true value turns on BBox's stream of consciousness.""" 
    28        self.spatialStore = spatialStore 29        self.spatialStore = spatialStore 
    29        self._verbose = verbose 30        self._verbose = verbose 
    30        self._visit_true = always_visit 31        self._visit_true = always_visit 
    31        self.model = rdfobj.Model(bbox.config.db,db='hash') 32        self.model = rdfobj.Model(bbox.config.db,db='hash') 
    32        self.model.load(bbox.config.boot) 33        self.model.load(bbox.config.boot) 
    33        from rdfobj import fbox 34        from rdfobj import fbox 
    34        fbox = rdfobj.fbox 35        fbox = rdfobj.fbox 
    35        counter = self.model.fetch(fbox.Visit_Count) 36        counter = self.model.fetch(fbox.Visit_Count) 
    36        c = counter[fbox.count] 37        c = counter[fbox.count] 
    37        if c is None: c = 0 38        if c is None: c = 0 
    38        c = int(str(c))+1 39        c = int(str(c))+1 
    39        counter[fbox.count] = str(c) 40        counter[fbox.count] = str(c) 
    40         41         
    41        if counter is None: 42        if counter is None: 
    42            v = self.model.create(fbox.Visit_Count, uri=fbox.Visit_Count) 43            v = self.model.create(fbox.Visit_Count, uri=fbox.Visit_Count) 
    43            v[fbox.count] = 0 44            v[fbox.count] = 0 
    44             45             
    45 46 
    46    def mention(self,thought): 47    def mention(self,thought): 
    47        """If BBox is constructed with verbose=1, prints to STDOUT (currently) a record of what it's up to.""" 48        """If BBox is constructed with verbose=1, prints to STDOUT (currently) a record of what it's up to.""" 
    48        if self._verbose: 49        if self._verbose: 
    49            print(thought) 50            print(thought) 
    50             51             
    51    def read_subscriptions(self): 52    def read_subscriptions(self): 
    52        """read_subscriptions() picks up the latest RSS feed updates. """ 53        """read_subscriptions() picks up the latest RSS feed updates. """ 
    53        self.mention("checking subscriptions.")  54        self.mention("checking subscriptions.")  
    54        subs = self.subscriptions() 55        subs = self.subscriptions() 
    55        from rdfobj import fbox 56        from rdfobj import fbox 
    56        fbox = rdfobj.fbox 57        fbox = rdfobj.fbox 
    57        for s in subs: 58        for s in subs: 
    58            self.mention("reading "+str(s[fbox.channel])) 59            self.mention("reading "+str(s[fbox.channel])) 
    59            format = s[fbox.format].uri() 60            format = s[fbox.format].uri() 
    60            c = s[fbox.channel] 61            c = s[fbox.channel] 
    61 62 
    62            # see if we're actually due a visit 63            # see if we're actually due a visit 
    63            due = self.visit_scheduled(s) 64            due = self.visit_scheduled(s) 
    64            if due is None:  65            if due is None:  
    65                print "nothing due to look at!" 66                print "nothing due to look at!" 
    66                subs.next() 67                subs.next() 
    67            else: 68            else: 
    68                if format == fbox.rss: 69                if format == fbox.rss: 
    69                    self.read_rss(c.uri(),subscription=s) 70                    self.read_rss(c.uri(),subscription=s) 
    70                elif format == fbox.rdf: 71                elif format == fbox.rdf: 
    71                    self.read_rdf(s[fbox.channel].uri(),subscription=s) 72                    self.read_rdf(s[fbox.channel].uri(),subscription=s) 
    72             73             
    73    def read_rss(self,uri,context=None,subscription=None): 74    def read_rss(self,uri,context=None,subscription=None): 
      75        """Read updates from an RSS feed.""" 
    74 76 
    75        #if subscription is None: subscription = {} 77        #if subscription is None: subscription = {} 
    76        rss = rdfobj.rss 78        rss = rdfobj.rss 
    77        dc = rdfobj.dc 79        dc = rdfobj.dc 
    78        ical = rdfobj.ical 80        ical = rdfobj.ical 
    79 81 
    80        result = self.politely_get_uri(uri,subscription=subscription)  82        result = self.politely_get_uri(uri,subscription=subscription)  
    81        channel = self.model.fetch(uri)          83        channel = self.model.fetch(uri)          
    82        """If we got a feed object back from the request, then create a 84        """If we got a feed object back from the request, then create a 
    83        context for this visit to the feed, and store the entries that we 85        context for this visit to the feed, and store the entries that we 
    84        collected from it.""" 86        collected from it.""" 
    85        if result['status'] != 200: return 87        if result['status'] != 200: return 
    86        feed = feedparser.parse(result['data']) 88        feed = feedparser.parse(result['data']) 
    87        if feed.has_key('feed'): 89        if feed.has_key('feed'): 
    88            context = self.visit(uri) 90            context = self.visit(uri) 
    89            # existence of exact duplicates? 91            # existence of exact duplicates? 
    90            items = [] 92            items = [] 
    91            for e in feed.entries: 93            for e in feed.entries: 
    92                item = self.model.create( rss.item, uri=str(e.link), context = context ) 94                item = self.model.create( rss.item, uri=str(e.link), context = context ) 
    93                if e.has_key('summary'): item[rss.description] = str(e.summary) 95                if e.has_key('summary'): item[rss.description] = str(e.summary) 
    94 96 
    95                if e.has_key('content'): item[rss.description] = str(e.content.value) 97                if e.has_key('content'): item[rss.description] = str(e.content.value) 
    96                # d.entries[0].modified_parsed is common 98                # d.entries[0].modified_parsed is common 
    97                time_tuple = e.modified_parsed 99                time_tuple = e.modified_parsed 
    98                         100                         
    99                # future experiments with pdis and better ical handling 101                # future experiments with pdis and better ical handling 
    100                # item[ical.datetime] = some process with time_tuple and strftime 102                # item[ical.datetime] = some process with time_tuple and strftime 
    101                # d = datetime.datetime(time_tuple) 103                # d = datetime.datetime(time_tuple) 
    102                # ical_date = ical_datetime.datetime_to_string(d) 104                # ical_date = ical_datetime.datetime_to_string(d) 
    103                # print ical_date 105                # print ical_date 
    104                # item[ical.datetime] = ical_date 106                # item[ical.datetime] = ical_date 
    105                 107                 
    106                ical_enough = time.strftime("%Y%m%dT%H%M%SZ",time_tuple) 108                ical_enough = time.strftime("%Y%m%dT%H%M%SZ",time_tuple) 
    107                item[ical.datetime] = ical_enough 109                item[ical.datetime] = ical_enough 
    108                items.append(item) 110                items.append(item) 
    109            for i in channel[rss.items]:      111             its = channel[rss.items]     
       112             for i in its:     
    110                items.append(i) 113                items.append(i) 
    111              114              
    112            channel[rss.items] = items 115            channel[rss.items] = items 
    113                 116                 
    114    117    
    115    def read_rdf(self,uri,subscription=None):    118    def read_rdf(self,uri,subscription=None):    
      119        """Read updates from an RDF url.""" 
    116        result = self.politely_get_uri(uri,subscription=subscription) 120        result = self.politely_get_uri(uri,subscription=subscription) 
    117        if result['status'] == 200: 121        if result['status'] == 200: 
    118            self.model.load(uri) 122            self.model.load(uri) 
    119 123 
    120    def politely_get_uri(self,uri,subscription=None): 124    def politely_get_uri(self,uri,subscription=None): 
    121         125        """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.""" 
    122        """Parse RDF feeds directly into Redland""" 126         
    123                   
    124        # we should deal with etag/last-mod politely here too @@TODO 127        # we should deal with etag/last-mod politely here too @@TODO 
    125        #visit = self.visit(uri) 128        #visit = self.visit(uri) 
    126        result = None 129        result = None 
    127        fbox = rdfobj.fbox 130        fbox = rdfobj.fbox 
    128        if subscription[fbox.last_etag] is not None: 131        if subscription[fbox.last_etag] is not None: 
    129            result = polite_request(str(uri),etag=str(subscription[fbox.last_etag])) 132            result = polite_request(str(uri),etag=str(subscription[fbox.last_etag])) 
    130        elif subscription[fbox.last_modified] is not None: 133        elif subscription[fbox.last_modified] is not None: 
    131         134         
    132            result = polite_request(str(uri),lastmodified=str(subscription[fbox.last_modified])) 135            result = polite_request(str(uri),lastmodified=str(subscription[fbox.last_modified])) 
    133        else: result = polite_request(str(uri)) 136        else: result = polite_request(str(uri)) 
    134        self.mention("received response: "+str(result['status'])) 137        self.mention("received response: "+str(result['status'])) 
    135            138            
    136        # look to the postgis index  139        # look to the postgis index  
    137        """Take actions about other kinds of HTTP statuses.(TODO)""" 140        """Take actions about other kinds of HTTP statuses.(TODO)""" 
    138        # handling different HTTP statuses. 141        # handling different HTTP statuses. 
    139        subscription[fbox.http_status] = str(result['status']) 142        subscription[fbox.http_status] = str(result['status']) 
    140        subscription[fbox.last_etag] = result['etag'] 143        subscription[fbox.last_etag] = result['etag'] 
    141        subscription[fbox.last_modified] = result['lastmodified']        144        subscription[fbox.last_modified] = result['lastmodified']        
    142        subscription[fbox.last_visited] = time.strftime("%Y%m%dT%H%M%SZ")  145        subscription[fbox.last_visited] = time.strftime("%Y%m%dT%H%M%SZ")  
    143        return result 146        return result 
    144 147 
    145    def subscriptions(self): 148    def subscriptions(self): 
    146        """subscriptions() returns a list (Iterator type) of the URLs at which 149        """Returns a list (Iterator type) of the URLs at which 
    147        there is a feed that we are subscribed to (fbox:Feed type)""" 150        there is a feed that we are subscribed to (fbox:Feed type)""" 
    148        from rdfobj import fbox, rdf 151        from rdfobj import fbox, rdf 
    149        fbox = rdfobj.fbox 152        fbox = rdfobj.fbox 
    150        rdf = rdfobj.rdf 153        rdf = rdfobj.rdf 
    151        subs = self.model.search(rdf.type,fbox.Feed) 154        subs = self.model.search(rdf.type,fbox.Feed) 
    152        return subs 155        return subs 
    153 156 
    154    def subscription(self,uri): 157    def subscription(self,uri): 
      158        """Given a uri, returns the rdfobj which is the subscription it represents.""" 
    155        obj = self.model.fetch(uri) 159        obj = self.model.fetch(uri) 
    156        return obj 160        return obj 
    157 161 
    158    def items(self,uri):  162     def items(self,uri,since=None,until=None): 
    159        from rdfobj import fbox  163         """Get items from a feed, optionally filtering by date. (not completely implemented)""" 
       164         from rdfobj import fbox, dc, rss 
       165         rss = fbox.rss 
       166         dc = fbox.dc 
    160        fbox = rdfobj.fbox 167        fbox = rdfobj.fbox 
    161        s = self.subscription(uri) 168        s = self.subscription(uri) 
    162        c = s[fbox.channel] 169        c = s[fbox.channel] 
      170        out = []  
      171        if since is not None: 
      172            for i in c[rss.items]: 
      173                warn(i[dc.date]) 
      174                if i[dc.date] > since: 
      175                    out.append(i) 
      176 
    163        return c[rss.items] 177        return c[rss.items] 
    164   178   
    165    def subscribe(self,feed=None,format=None,interval=None): 179    def subscribe(self,feed=None,format=None,interval=None): 
    166        """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.""" 180        """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.""" 
    167        from rdfobj import fbox 181        from rdfobj import fbox 
    168        fbox = rdfobj.fbox       182        fbox = rdfobj.fbox       
    169        if feed is None: return 183        if feed is None: return 
    170 184 
    171        f = self.model.search(fbox.channel,feed) 185        f = self.model.search(fbox.channel,feed) 
    172        found = None 186        found = None 
    173        for n in f: 187        for n in f: 
    174            found = 1     188            found = 1     
    175        if found is not None: 189        if found is not None: 
    176            return 190            return 
    177 191 
    178        self.mention("subscribing to "+str(feed)) 192        self.mention("subscribing to "+str(feed)) 
    179 193 
    180        if format is None: format = fbox.rdf 194        if format is None: format = fbox.rdf 
    181 195 
    182        if interval is None: interval = str(100) 196        if interval is None: interval = str(100) 
    183 197 
    184        ff = self.model.create( fbox.Feed, uri=None ) 198        ff = self.model.create( fbox.Feed, uri=None ) 
    185        ff[fbox.channel] = str(feed) 199        ff[fbox.channel] = str(feed) 
    186        ff[fbox.format] = str(format) 200        ff[fbox.format] = str(format) 
    187        ff[fbox.interval] = interval 201        ff[fbox.interval] = interval 
    188 202 
    189        return ff 203        return ff 
    190 204 
    191    def update(self): 205    def update(self): 
    192        """update() causes all the subscribed URLs to be visited for updates.""" 206        """Causes all the subscribed URLs to be visited for updates.""" 
    193        subs = self.subscriptions() 207        subs = self.subscriptions() 
    194        from rdfobj import fbox 208        from rdfobj import fbox 
    195        fbox = rdfobj.fbox 209        fbox = rdfobj.fbox 
    196        while not subs.end(): 210        while not subs.end(): 
    197            s = subs.current() 211            s = subs.current() 
    198            self.visit(s[fbox.channel]) 212            self.visit(s[fbox.channel]) 
    199            subs.next() 213            subs.next() 
    200 214 
    201    def visit(self,uri=None): 215    def visit(self,uri=None): 
    202        """visit() creates an anonymous object which records a visit that we 216        """Creates an anonymous object which records a visit that we 
    203        paid to a feed, including a counter of times visited. This object is 217        paid to a feed, including a counter of times visited. This object is 
    204        used as a Redland context for all the information collected from a feed 218        used as a Redland context for all the information collected from a feed 
    205        during this visit.""" 219        during this visit.""" 
    206        # redland had problems serialising models with bnode context uris  220        # redland had problems serialising models with bnode context uris  
    207        count = self.counter() 221        count = self.counter() 
    208        from rdfobj import fbox 222        from rdfobj import fbox 
    209        fbox = rdfobj.fbox 223        fbox = rdfobj.fbox 
    210        visit_uri = str(fbox.visit)+'/'+str(count) 224        visit_uri = str(fbox.visit)+'/'+str(count) 
    211        visit = self.model.create( fbox.Visit , visit_uri) 225        visit = self.model.create( fbox.Visit , visit_uri) 
    212 226 
    213        visit[fbox.source] = uri 227        visit[fbox.source] = uri 
    214        t = time.strftime("%Y%m%dT%H%M%SZ") 228        t = time.strftime("%Y%m%dT%H%M%SZ") 
    215        visit[fbox.timestamp] = t 229        visit[fbox.timestamp] = t 
    216        return RDF.Node(RDF.Uri(str(visit.uri()))) 230        return RDF.Node(RDF.Uri(str(visit.uri()))) 
    217 231 
    218    def user(self,token=None,nick=None,mbox=None): 232    def user(self,token=None,nick=None,mbox=None): 
    219        """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!""" 233        """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!""" 
    220        from rdfobj import foaf 234        from rdfobj import foaf 
    221        foaf = rdfobj.foaf 235        foaf = rdfobj.foaf 
    222        if token is not None: 236        if token is not None: 
    223            users = self.model.search(foaf.auth_token,token) 237            users = self.model.search(foaf.auth_token,token) 
    224            for u in users: 238            for u in users: 
    225                return u[foaf.alias] 239                return u[foaf.alias] 
    226        if mbox is not None: 240        if mbox is not None: 
    227            users = self.model.search(foaf.mbox,mbox) 241            users = self.model.search(foaf.mbox,mbox) 
    228            for u in users: 242            for u in users: 
    229                return u 243                return u 
    230        if nick is not None: 244        if nick is not None: 
    231            o = [] 245            o = [] 
    232            users = self.model.search(foaf.name,nick) 246            users = self.model.search(foaf.name,nick) 
    233            for u in users: 247            for u in users: 
    234                o.append(u) 248                o.append(u) 
    235            users = self.model.search(foaf.givenName,nick) 249            users = self.model.search(foaf.givenName,nick) 
    236            for u in users: o.append(u) 250            for u in users: o.append(u) 
    237            return o 251            return o 
    238 252 
    239    def add_user(self,nick=None,mbox=None,password=None): 253    def add_user(self,nick=None,mbox=None,password=None): 
    240        """ Create a new user foaf:Person""" 254        """ Create a new user foaf:Person""" 
    241        store = self.store 255        store = self.store 
    242        from rdfobj import foaf, wlan 256        from rdfobj import foaf, wlan 
    243        foaf = rdfobj.foaf 257        foaf = rdfobj.foaf 
    244        wlan = rdfobj.wlan 258        wlan = rdfobj.wlan 
    245 259 
    246        obj = self.model.create(foaf.Person,uri=uri) 260        obj = self.model.create(foaf.Person,uri=uri) 
    247        obj[foaf.mbox] = mbox 261        obj[foaf.mbox] = mbox 
    248        obj[foaf.nick] = nick 262        obj[foaf.nick] = nick 
    249        if page is not None: obj[foaf.homepage] = page 263        if page is not None: obj[foaf.homepage] = page 
    250        self.obj = obj 264        self.obj = obj 
    251 265 
    252        """ 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. """ 266        """ 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. """ 
    253 267 
    254        auth = store.create(foaf.AuthedPerson) 268        auth = store.create(foaf.AuthedPerson) 
    255        auth[foaf.password] = password 269        auth[foaf.password] = password 
    256        auth[foaf.nick] = nick 270        auth[foaf.nick] = nick 
    257        auth[foaf.alias] = obj 271        auth[foaf.alias] = obj 
    258 272 
    259        token = self.auth_token() 273        token = self.auth_token() 
    260        auth[foaf.auth_token] = token 274        auth[foaf.auth_token] = token 
    261        self.model.sync() 275        self.model.sync() 
    262        return token 276        return token 
    263 277 
    264    def auth_token(self): 278    def auth_token(self): 
      279        """Generate a random auth token.""" 
    265        x = '' 280        x = '' 
    266        for  n in range(0, 6): 281        for  n in range(0, 6): 
    267            x = x + chr(65 + random.randint(0, 26)) 282            x = x + chr(65 + random.randint(0, 26)) 
    268        return x         283        return x         
    269         284         
    270    def visit_scheduled(self,sub): 285    def visit_scheduled(self,sub): 
    271        """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."""    286        """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."""    
    272        if self._visit_true is not None: 287        if self._visit_true is not None: 
    273            return 1 288            return 1 
    274        from rdfobj import fbox 289        from rdfobj import fbox 
    275        fbox = rdfobj.fbox 290        fbox = rdfobj.fbox 
    276        last = sub[fbox.last_visited] 291        last = sub[fbox.last_visited] 
    277        if last is None: 292        if last is None: 
    278            return 1 293            return 1 
    279        t = time.time() 294        t = time.time() 
    280        # convert last time simply from ical to epoch? 295        # convert last time simply from ical to epoch? 
    281 296 
    282        return 1 297        return 1 
    283        since = t - float(str(last)) 298        since = t - float(str(last)) 
    284         299         
    285        interval = sub[fbox.interval] 300        interval = sub[fbox.interval] 
    286        if interval is None: 301        if interval is None: 
    287            sub[fbox.interval] = str(100) 302            sub[fbox.interval] = str(100) 
    288            interval = sub[fbox.interval] 303            interval = sub[fbox.interval] 
    289        secs = int(str(interval))*60 304        secs = int(str(interval))*60 
    290        if since >= secs: 305        if since >= secs: 
    291            return 1 306            return 1 
    292        return None 307        return None 
    293                 308                 
    294    def counter(self): 309    def counter(self): 
      310        """Update the counter that's used to generate visit context URIs.""" 
    295        from rdfobj import fbox 311        from rdfobj import fbox 
    296        fbox = rdfobj.fbox 312        fbox = rdfobj.fbox 
    297        counter = self.model.fetch(fbox.Visit_Count) 313        counter = self.model.fetch(fbox.Visit_Count) 
    298        c = counter[fbox.count] 314        c = counter[fbox.count] 
    299        c = int(str(c))+1 315        c = int(str(c))+1 
    300        counter[fbox.count] = str(c) 316        counter[fbox.count] = str(c) 
    301        return c 317        return c 
    302 318 
    303 319 
    304if __name__ == "__main__": 320if __name__ == "__main__": 
    305    bbox = BBox(visit_true = 1) 321    bbox = BBox(visit_true = 1) 
    306    bbox.subscribe(feed='http://frot.org/wirelesslondon/bbox.rdf',format=fbox.rss) 322    bbox.subscribe(feed='http://frot.org/wirelesslondon/bbox.rdf',format=fbox.rss) 
    307    bbox.subscribe(feed='http://frot.org/devlog/index.rss',format=fbox.rss) 323    bbox.subscribe(feed='http://frot.org/devlog/index.rss',format=fbox.rss) 
    308    bbox.subscribe(feed='http://zooleika.org.uk/bio/foaf.rdf',format=fbox.rdf) 324    bbox.subscribe(feed='http://zooleika.org.uk/bio/foaf.rdf',format=fbox.rdf) 
    309    bbox.read_subscriptions() 325    bbox.read_subscriptions() 
  • bbox/bbox/config.py

    Revision 47 Revision 103
    1boot='file:/home/wirelesslondon/lib/consumotronic/bbox/boot.rdf' 1boot='file:/home/jo/consumotronic/bbox/boot.rdf' 
    2store = '/home/wirelesslondon/store/' 2store = '/home/jo/bbox/store/' 
    3db = 'bbox' 3db = 'bbox' 
      4spatialdb = 'bbox' 
      5utm_zone = '31' 
      6srid = '32631' 
  • bbox/bbox/spatialStore.py

    Revision 56 Revision 103
    1"""provides the unified data store between redland RDF and postGIS index""" 1"""provides the unified data store between redland RDF and spatial (for now PostGIS) index""" 
    2import pgdb 2import pgdb 
    3import re 3import re 
    4import warnings 4import warnings 
    5import bbox.config 5import bbox.config 
    6 6 
    7_database = bbox.config._spatialdb 7_database = bbox.config._spatialdb 
    8_utm_zone = bbox.config._utm_zone 8_utm_zone = bbox.config._utm_zone 
    9_srid = bbox.config._srid  9_srid = bbox.config._srid  
    10 10 
    11class SpatialStore: 11class SpatialStore: 
    12 12 
    13    def __init__(self,vars = None,database=None): 13    def __init__(self,vars = None,database=None): 
      14        """Create a new store interface to a certain database name, passed in here or defaulting to what's set in bbox.config._spatialdb""" 
    14        self.vars = vars 15        self.vars = vars 
    15        if database is None:     16        if database is None:     
    16            database = _database 17            database = _database 
    17             18             
    18        db = pgdb.connect(database=database) 19        db = pgdb.connect(database=database) 
    19        self.db = db 20        self.db = db 
    20 21 
    21    def geom(self,uri): 22    def geom(self,uri): 
    22        """We are passed the uri of something in the DB""" 23        """Passed the uri of something in the DB, returns geometry for it as a dict; if the geom is a point, {'x':x,'y':y}; if the geom is a line, x1,y1,x2,y2; polygon geom isn't really supported yet.""" 
    23        # again we have an issue of what kind of geom this is 24        # again we have an issue of what kind of geom this is 
    24        select = "select GeometryType(geom) from nodes where node='"+uri+"'" 25        select = "select GeometryType(geom) from nodes where node='"+uri+"'" 
    25        db = self.db.cursor() 26        db = self.db.cursor() 
    26        db.execute(select) 27        db.execute(select) 
    27        g = db.fetchone() 28        g = db.fetchone() 
    28        geom = {} 29        geom = {} 
    29        if g[0] == 'LINESTRING': 30        if g[0] == 'LINESTRING': 
    30            select = "SELECT name, type, X(StartPoint(geom)) as x, Y(StartPoint(geom)), GeometryFromText(geom) as y, X(EndPoint(geom)), Y(EndPoint(geom)) from nodes where node='"+uri+"'" 31            select = "SELECT name, type, X(StartPoint(geom)) as x, Y(StartPoint(geom)), GeometryFromText(geom) as y, X(EndPoint(geom)), Y(EndPoint(geom)) from nodes where node='"+uri+"'" 
    31            db.execute(select) 32            db.execute(select) 
    32            c = db.fetchone() 33            c = db.fetchone() 
    33            geom['x1'] = c[2] 34            geom['x1'] = c[2] 
    34            geom['x2'] = c[4] 35            geom['x2'] = c[4] 
    35            geom['y1'] = c[3] 36            geom['y1'] = c[3] 
    36            geom['y2'] = c[5] 37            geom['y2'] = c[5] 
    37 38 
    38        elif g[0] == 'POINT': 39        elif g[0] == 'POINT': 
    39            select = "SELECT name,type,X(geom),Y(geom) from nodes where node='"+uri+"'" 40            select = "SELECT name,type,X(geom),Y(geom) from nodes where node='"+uri+"'" 
    40            db.execute(select) 41            db.execute(select) 
    41            c = db.fetchone() 42            c = db.fetchone() 
    42            geom['x'] = c[2] 43            geom['x'] = c[2] 
    43            geom['y'] = c[3] 44            geom['y'] = c[3] 
    44 45 
    45        elif g[0] == 'POLYGON': 46        elif g[0] == 'POLYGON': 
      47            #@@TODO support this properly, as vectors, whatever 
    46            select = 'SELECT GeometryAsText(geom) where node = \''+uri+"'"       48            select = 'SELECT GeometryAsText(geom) where node = \''+uri+"'"       
    47         49         
    48        return geom 50        return geom 
    49 51 
    50    def add_geom(self,uri=None,name=None,type=None,rdf_type=None,status=None,owner=None,x=None,y=None,z=None,date=None): 52    def add_geom(self,uri=None,name=None,type=None,rdf_type=None,status=None,owner=None,x=None,y=None,z=None,date=None): 
    51        """ Add geometry for a Uri into a spatial index. needs uri and also name=[name] must be supplied.""" 53        """ Add geometry for a Uri into a spatial index. needs uri and also name=[name] must be supplied.""" 
    52        if uri is None: return 54        if uri is None: return 
    53        if type is None: type = '' 55        if type is None: type = '' 
    54         56         
    55        if rdf_type is None: 57        if rdf_type is None: 
    56            rdf_type = 'http://www.w3.org/2003/01/geo/wgs84_pos#SpatialThing' 58            rdf_type = 'http://www.w3.org/2003/01/geo/wgs84_pos#SpatialThing' 
    57        if status is None: status = '' 59        if status is None: status = '' 
    58        if owner is None: owner = 'wl@frot.org' 60        if owner is None: owner = 'wl@frot.org' 
    59        if date is None: 61        if date is None: 
    60            date = 'now()' 62            date = 'now()' 
    61        else: 63        else: 
    62            date = "'"+str(date)+"'" 64            date = "'"+str(date)+"'" 
    63         65         
    64        geom = "GeometryFromText('POINT("+str(x)+' '+str(y)+")',"+str(_srid)+")" 66        geom = "GeometryFromText('POINT("+str(x)+' '+str(y)+")',"+str(_srid)+")" 
    65         67         
    66        #insert = "INSERT INTO nodes (node,geom,name,status,rdf_type,type,created) values ('"+str(uri)+"',"+geom+",'"+str(name)+"','"+str(status)+"','"+str(rdf_type)+"','"+str(type)+"',"+date+')' 68        #insert = "INSERT INTO nodes (node,geom,name,status,rdf_type,type,created) values ('"+str(uri)+"',"+geom+",'"+str(name)+"','"+str(status)+"','"+str(rdf_type)+"','"+str(type)+"',"+date+')' 
    67        #print insert    69        #print insert    
    68 70 
    69        db = self.db 71        db = self.db 
    70        db.cursor().execute("INSERT INTO nodes (node,geom,name,status,rdf_type,type,created) values (%s,"+geom+",%s,%s,%s,%s,"+date+")", (str(uri),str(name),str(status),str(rdf_type),str(type))) 72        db.cursor().execute("INSERT INTO nodes (node,geom,name,status,rdf_type,type,created) values (%s,"+geom+",%s,%s,%s,%s,"+date+")", (str(uri),str(name),str(status),str(rdf_type),str(type))) 
    71        db.commit() 73        db.commit() 
    72        #print "new geometry for "+str(uri) 74        #print "new geometry for "+str(uri) 
    73 75 
    74    def find_near(self,node=None, lat=None,lon=None,x=None,y=None,r=None,type=None,terse=None): 76    def find_near(self,node=None, lat=None,lon=None,x=None,y=None,r=None,type=None,terse=None): 
    75        if lat is not None: 77        if lat is not None: 
    76            thing = wl.spatialThing.SpatialThing(spatialStore = self) 78            thing = wl.spatialThing.SpatialThing(spatialStore = self) 
    77            (x,y) = thing.latlon_to_utm(lat=lat,lon=lon) 79            (x,y) = thing.latlon_to_utm(lat=lat,lon=lon) 
    78        if r is None: 80        if r is None: 
    79            r = 1600  81            r = 1600  
    80        found = self.within_box(minx=x-r,miny=y-r,maxx=x+r,maxy=y+r,rdf_type=type,terse=terse) 82        found = self.within_box(minx=x-r,miny=y-r,maxx=x+r,maxy=y+r,rdf_type=type,terse=terse) 
    81        return found 83        return found 
    82     84     
    83    def within_box(self,minx=None,miny=None,maxx=None,maxy=None,type=None,rdf_type=None,terse=None): 85    def within_box(self,minx=None,miny=None,maxx=None,maxy=None,type=None,rdf_type=None,terse=None): 
    84        """Accepts a minx,miny,maxx,maxy bounding box; should return a list of spatialThings, unless you specify terse=1, in whih case you just get a array of dicts built from the postgis db contents.""" 86        """Accepts a minx,miny,maxx,maxy bounding box; should return a list of spatialThings, unless you specify terse=1, in whih case you just get a array of dicts built from the postgis db contents.""" 
    85             87             
    86        select = "SELECT node, name, rdf_type, X(geom) as x, Y(geom) as y FROM nodes where Within(geom,GeometryFromText('POLYGON(("+str(minx)+' '+str(miny)+','+str(minx)+' '+str(maxy)+','+str(maxx)+' '+str(maxy)+','+str(maxx)+' '+str(miny)+','+str(minx)+' '+str(miny)+"))',"+str(_srid)+"))" 88        select = "SELECT node, name, rdf_type, X(geom) as x, Y(geom) as y FROM nodes where Within(geom,GeometryFromText('POLYGON(("+str(minx)+' '+str(miny)+','+str(minx)+' '+str(maxy)+','+str(maxx)+' '+str(maxy)+','+str(maxx)+' '+str(miny)+','+str(minx)+' '+str(miny)+"))',"+str(_srid)+"))" 
    87        warnings.warn( select ) 89        warnings.warn( select ) 
    88        """Also optionally filter by type""" 90        """Also optionally filter by type""" 
    89        if type is not None: 91        if type is not None: 
    90            select = select+' AND type=\''+str(type)+'\'' 92            select = select+' AND type=\''+str(type)+'\'' 
    91        if rdf_type is not None: 93        if rdf_type is not None: 
    92            select = select+' AND rdf_type=\''+str(rdf_type)+'\'' 94            select = select+' AND rdf_type=\''+str(rdf_type)+'\'' 
    93             95             
    94        db = self.db 96        db = self.db 
    95        c = db.cursor() 97        c = db.cursor() 
    96        c.execute(select) 98        c.execute(select) 
    97        geoms = c.fetchall() 99        geoms = c.fetchall() 
    98        things = [] 100        things = [] 
    99        if terse is not None: 101        if terse is not None: 
    100            for g in geoms: 102            for g in geoms: 
    101                things.append({'uri':g[0],'name':g[1],'type':g[2],'x':g[3],'y':g[4]}) 103                things.append({'uri':g[0],'name':g[1],'type':g[2],'x':g[3],'y':g[4]}) 
    102        else: 104        else: 
    103            for g in geoms: 105            for g in geoms: 
    104                things.append(g[0]) 106                things.append(g[0]) 
    105         107         
    106        """Return a rdfobj node/object for each geometry.""" 108        """Return a rdfobj node/object for each geometry.""" 
    107        return things 109        return things 
    108 110 
    109    def recent(self,days=None,type=None): 111    def recent(self,days=None,type=None): 
    110        if days is None: days = 30 112        if days is None: days = 30 
    111        if type is None: type = 'http://xmlns.com/2003/wireless/Node' 113        if type is None: type = 'http://xmlns.com/2003/wireless/Node' 
    112        query = "SELECT node from nodes where (now() - interval '"+str(days)+" days') <= created and rdf_type = '"+type+"'" 114        query = "SELECT node from nodes where (now() - interval '"+str(days)+" days') <= created and rdf_type = '"+type+"'" 
    113        db = self.db 115        db = self.db 
    114        c = db.cursor() 116        c = db.cursor() 
    115        c.execute(query) 117        c.execute(query) 
    116        nodes = c.fetchall() 118        nodes = c.fetchall() 
    117        urls = [] 119        urls = [] 
    118        for n in nodes: 120        for n in nodes: 
    119            urls.append(n[0]) 121            urls.append(n[0]) 
    120        return urls 122        return urls 
    121             123             
    122    def update_geom(self,x=None,y=None,uri=None): 124    def update_geom(self,x=None,y=None,uri=None): 
    123        if x is None: return 125        if x is None: return 
    124        if uri is None: return 126        if uri is None: return 
    125 127 
    126        db = self.db 128        db = self.db 
    127        c = db.cursor() 129        c = db.cursor() 
    128        c.execute("UPDATE nodes SET geom = GeometryFromText('POINT("+str(x)+' '+str(y)+")',"+str(_srid)+") where node ='%s'",(str(uri))) 130        c.execute("UPDATE nodes SET geom = GeometryFromText('POINT("+str(x)+' '+str(y)+")',"+str(_srid)+") where node ='%s'",(str(uri))) 
    129        db.commit() 131        db.commit() 
    130         132         
    131      133      
  • bbox/bbox/ui/__init__.py

    Revision 48 Revision 103
    1from Cheetah.Template import Template 1from Cheetah.Template import Template 
    2from bbox import BBox 2from bbox import BBox 
    3#from spatialStore import Store 3#from spatialStore import Store 
    4import quixote 4import quixote 
    5import os  5import os  
    6import RDF 6import RDF 
    7import rdfobj 7import rdfobj 
    8 8 
    9_q_exports = ['_q_index','subs','_q_lookup','getitems'9_q_exports = ['_q_index','subs','_q_lookup','getitems','items'
    10 10 
    11def _q_index(r): 11def _q_index(r): 
    12    return 'bbox' 12    return 'bbox' 
    13 13 
    14def subs(r): 14def subs(r): 
    15    """List current subscriptions.""" 15    """List current subscriptions.""" 
    16    bb = BBoxUI() 16    bb = BBoxUI() 
    17    return bb.subs(r) 17    return bb.subs(r) 
    18 18 
    19def read_subs(r): 19def read_subs(r): 
    20    """Trigger a read of updates to subscriptions, if found.""" 20    """Trigger a read of updates to subscriptions, if found.""" 
    21    bb = BBoxUI() 21    bb = BBoxUI() 
    22    return bb.read_subs(r) 22    return bb.read_subs(r) 
    23 23 
    24def _q_lookup(r,n): 24def _q_lookup(r,n): 
    25    pass 25    pass 
    26 26 
    27#def _q_access(request): 27#def _q_access(request): 
    28#    raise UnauthorizedError(realm='Foo Realm') 28#    raise UnauthorizedError(realm='Foo Realm') 
    29 29 
    30class BBoxUI: 30class BBoxUI: 
    31    def __init__(self): 31    def __init__(self): 
    32        self.bbox = BBox('bbox.db') 32        self.bbox = BBox('bbox.db') 
    33 33 
    34    def subscribe(self,r):  34     def subscribe(self,r,uri=None): 
       35         """Add a subscription to a uri, either as a query param or passed in as 'uri'.""" 
    35        uri = r.get_form_var('uri') 36        uri = r.get_form_var('uri') 
    36        self.bbox.subscribe(uri=uri) 37        self.bbox.subscribe(uri=uri) 
    37        context = self.bbox.visit(uri) 38        context = self.bbox.visit(uri) 
    38        self.bbox.read(uri,context=context) 39        self.bbox.read(uri,context=context) 
    39 40 
    40    def read_subs(self,r): 41    def read_subs(self,r): 
    41        self.bbox.read_subscriptions() 42        self.bbox.read_subscriptions() 
    42 43 
    43    def subs(self,r): 44    def subs(self,r): 
    44        subs = self.bbox.subscriptions() 45        subs = self.bbox.subscriptions() 
    45        xml = self.serialise(subs) 46        xml = self.serialise(subs) 
    46        return xml 47        return xml 
    47 48 
    48    def getitems(self,r): 49    def getitems(self,r): 
    49        """This is the bloglines-style interface""" 50        """This is the bloglines-style interface""" 
    50        """See http://www.bloglines.com/services/api/getitems """     51        """See http://www.bloglines.com/services/api/getitems """     
    51         52         
    52        read = r.get_form_var('n') 53        read = r.get_form_var('n') 
    53        """ 'n' parameter - 1 marks downloaded items as read, 0 does not.""" 54        """ 'n' parameter - 1 marks downloaded items as read, 0 does not.""" 
    54 55 
    55        date = r.get_form_var('d') 56        date = r.get_form_var('d') 
    56        """ 'd' parameter - date to get new entries since. """ 57        """ 'd' parameter - date to get new entries since. """ 
    57 58 
    58        s = r.get_form_var('s') 59        s = r.get_form_var('s') 
    59         60         
    60        """ 'subid'. we will accept an integer id which is a unique key in the node table, but this parameter is optional instead of compulsory; you can otherwise supply a URI-encoded url...""" 61        """ 'subid'. we will accept an integer id which is a unique key in the node table, but this parameter is optional instead of compulsory; you can otherwise supply a URI-encoded url...""" 
    61        #sub = bbox.subscription(s) 62        #sub = bbox.subscription(s) 
    62        items = bbox.items(s) 63        items = self.bbox.items(s) 
    63        out = '' 64        out = '' 
    64        for i in items: 65        for i in items: 
    65            out = out + str(i) + "\n" 66            out = out + str(i) + "\n" 
    66 67 
    67        if d: 68        if d: 
    68            pass  69            pass  
    69             70             
    70        uri = r.get_form_var('uri') 71        uri = r.get_form_var('uri') 
    71        if uri is None: uri = r.get_form_var('u') 72        if uri is None: uri = r.get_form_var('u') 
    72          73          
    73 74 
    74 75 
    75    """HTTP Status Response 76    """HTTP Status Response 
    76 77 
    77    * 200 - normal 78    * 200 - normal 
    78    * 304 - the request produced no entries (either there were no unread entries, or no entries after the given date, depending on the parameters passed in) 79    * 304 - the request produced no entries (either there were no unread entries, or no entries after the given date, depending on the parameters passed in) 
    79    * 401 - incorrect email address or password 80    * 401 - incorrect email address or password 
    80    * 403 - invalid or missing BloglinesSubId 81    * 403 - invalid or missing BloglinesSubId 
    81    * 410 - subscription has been deleted 82    * 410 - subscription has been deleted 
    82    """ 83    """ 
    83 84 
    84    def serialise(self,objs): 85    def serialise(self,objs): 
    85        """Serialises a list of rdf objects, supplied, as RDF/XML."""  86        """Serialises a list of rdf objects, supplied, as RDF/XML."""  
    86        from rdfobj import dc 87        from rdfobj import dc 
    87        dc = rdfobj.dc 88        dc = rdfobj.dc 
    88        out = '' 89        out = '' 
    89        # proper serialisation 90        # proper serialisation 
    90        tmpstore = RDF.MemoryStorage() 91        tmpstore = RDF.MemoryStorage() 
    91        tmpmodel = RDF.Model(tmpstore) 92        tmpmodel = RDF.Model(tmpstore) 
    92        for o in objs: