Changeset 4167 for exist


Ignore:
Timestamp:
03/09/08 12:33:30 (11 years ago)
Author:
cbyrom
Message:

Pull out common constants into module level variables, to improve
consistency + re-use. Fix setting of some config file defaults.
Extend test suite + improve - add checks for config files and use
module level variables.

Location:
exist/trunk/python/ndgUtils
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • exist/trunk/python/ndgUtils/ndgObject.py

    r4160 r4167  
    11import ndgRetrieve,xmlHandler2, logging 
    22 
     3# The various different document types: 
     4MOLES_DOC_TYPE = 'NDG-B0' 
     5DIF_DOC_TYPE = 'DIF' 
     6DC_DOC_TYPE = 'DC' 
     7ISO_DOC_TYPE = 'ISO19139' 
     8NDGB1_DOC_TYPE = 'NDG-B1' 
     9NDGB0_DOC_TYPE = 'NDG-B0' 
     10MDIP_DOC_TYPE = 'MDIP' 
     11NDGA0_DOC_TYPE = 'NDG-A0' 
     12NUMSIM_DOC_TYPE = 'NumSim' 
     13ATOM_DOC_TYPE = 'ATOM' 
     14ATOM_BACKUP_DOC_TYPE = 'ATOM-BACKUP' 
     15     
     16# Group the doc types according to the source they should be retrieved from 
     17DISCOVERY_SCHEMES = ['DIF','DC','MDIP','ISO19139', 'ISO'] 
     18BROWSE_SCHEMES = ['NDG-B0','NDG-B1','NumSim', 'ATOM', 'ATOM-BACKUP'] 
     19 
    320class ndgObject: 
    4     ''' This class instantiates an ndgObject which describes the various ways of obtaining itself, primarily 
     21    ''' This class instantiates an ndgObject which describes the various ways  
     22        of obtaining itself, primarily 
    523                (1) a downloadable xml representation from a repository, 
    624                (2) a printable xml representation ''' 
     
    6785        self.viewService='%s/view/'%server 
    6886        discoveryBASE='%s%s__%s__%s'%(self.viewService,self.repository,self.schema,self.localID) 
    69         fmt=self.config.get('DISCOVERY','formatDefault','DIF') 
     87 
     88        # set default return format - if not set, just return in original format 
     89        fmt='' 
     90        if self.config.has_option('DISCOVERY','formatDefault'): 
     91            fmt=self.config.get('DISCOVERY','formatDefault') 
     92            logging.info("Default discovery format set to: %s" %fmt) 
     93             
    7094        self.BURL=None 
    7195        # We'll build the following even if it can't be used (as would be the case for 
     
    7599        servicehost=self.config.get('NDG_B_SERVICE',self.repository,'unknown') 
    76100        # If this record is itself a discovery record, then we don't have much more to do 
    77         if self.schema in ['DIF','DC','MDIP','ISO19139']: 
     101        if self.schema in DISCOVERY_SCHEMES: 
    78102            self.xmlURL=self.__buildURL( 
    79103                discoveryBASE.replace('/view/','/retrieve/'),[qs,('format','raw')]) 
     
    81105            self.URL=self.discoveryURL 
    82106            if servicehost!='unknown':self.gettable=1 
    83         elif self.schema in ['NDG-B0','NDG-B1','NumSim']: 
     107        elif self.schema in BROWSE_SCHEMES: 
    84108            # One day we'll use a service binding to get this 
    85109            # This is a mapping from the ndg repository id to an actual repository id 
  • exist/trunk/python/ndgUtils/ndgRetrieve.py

    r4165 r4167  
    22from ndgSearch import ndgSearch 
    33import cgi,time, logging 
     4import ndgObject as no 
     5 
     6MOLES_COLLECTION = '/db/ndg_B_metadata' 
     7ATOM_COLLECTION = '/db/atoms' 
     8ATOM_BACKUP_COLLECTION = '/db/atoms_backup' 
     9NUMSIM_COLLECTION = '/db/NumSim' 
     10NDGA0_COLLECTION = '/db/ndg_A_metadata' 
    411 
    512class NDGRetrieveError(Exception): 
     
    3542        if discovery: 
    3643            logging.debug("Using Discovery service") 
    37             if uri.schema in ['ISO','DIF','MDIP']: 
     44            if uri.schema in no.DISCOVERY_SCHEMES: 
    3845                ws=ndgSearch() 
    3946            else: 
     
    7481    if not status: return status,ws 
    7582            
    76     if uri.schema=='NDG-A0': 
    77         target='/db/ndg_A_metadata' 
    78     elif uri.schema=='NumSim': 
    79         target='/db/NumSim' 
    80     elif uri.schema == 'ATOM': 
    81         target='/db/atoms' 
    82     elif uri.schema == 'ATOM-BACKUP': 
    83         target='/db/atoms_backup' 
     83    if uri.schema==no.NDGA0_DOC_TYPE: 
     84        target = NDGA0_COLLECTION 
     85    elif uri.schema == no.NUMSIM_DOC_TYPE: 
     86        target = NUMSIM_COLLECTION 
     87    elif uri.schema == no.ATOM_DOC_TYPE: 
     88        target = ATOM_COLLECTION  
     89    elif uri.schema == no.ATOM_BACKUP_DOC_TYPE: 
     90        target = ATOM_BACKUP_COLLECTION  
    8491    else: 
    85         target='/db/ndg_B_metadata' 
     92        target = MOLES_COLLECTION 
    8693    
    8794    #do the actual retrieve: 
  • exist/trunk/python/ndgUtils/test.config

    r4165 r4167  
    66badc.nerc.ac.uk:chinook.badc.rl.ac.uk 
    77www.npm.ac.uk:www.npm.ac.uk 
     8instance:chinook.badc.rl.ac.uk 
    89 
    910[NDG_EXIST] 
     
    1112badc.nerc.ac.uk:chinook.badc.rl.ac.uk 
    1213 
     14[DISCOVERY] 
     15default:ndgbeta.badc.rl.ac.uk 
     16 
  • exist/trunk/python/ndgUtils/test_DocumentRetrieve.py

    r4165 r4167  
    1515import logging, unittest 
    1616import DocumentRetrieve 
     17import ndgRetrieve as nr 
    1718import testconstants as tc 
     19import ndgObject as no 
    1820 
    1921logging.basicConfig(level=logging.DEBUG, 
     
    4749                                       proxyServer="http://wwwcache3.rl.ac.uk:8080/") 
    4850        doc = dr.get(tc.VALID_PROVIDER_ID, \ 
    49                      tc.MOLES_DOC_TYPE, tc.VALID_MOLES_DOC_ID) 
     51                     no.MOLES_DOC_TYPE, tc.VALID_MOLES_DOC_ID) 
    5052        self.assertNotEquals(doc.find(tc.VALID_MOLES_DOC_ID), -1) 
    5153         
    5254    def testGetDIF(self): 
    5355        dr = DocumentRetrieve.DocumentRetrieve(tc.VALID_REPOSITORY) 
    54         doc = dr.get(tc.VALID_PROVIDER_ID, tc.DIF_DOC_TYPE, \ 
    55                      tc.VALID_MOLES_DOC_ID, targetCollection = tc.MOLES_COLLECTION) 
     56        doc = dr.get(tc.VALID_PROVIDER_ID, no.DIF_DOC_TYPE, \ 
     57                     tc.VALID_MOLES_DOC_ID, targetCollection = nr.MOLES_COLLECTION) 
    5658        self.assertNotEquals(doc.find(tc.VALID_MOLES_DOC_ID), -1) 
    5759         
    58     def offtestGetMDIP(self): 
     60    def OFFtestGetMDIP(self): 
    5961        # TODO: switch back on once the MDIP tranform has been fixed 
    6062        dr = DocumentRetrieve.DocumentRetrieve(tc.VALID_REPOSITORY) 
    61         doc = dr.get(tc.VALID_PROVIDER_ID, tc.MDIP_DOC_TYPE, \ 
    62                      tc.VALID_MOLES_DOC_ID, targetCollection = tc.MOLES_COLLECTION) 
     63        doc = dr.get(tc.VALID_PROVIDER_ID, no.MDIP_DOC_TYPE, \ 
     64                     tc.VALID_MOLES_DOC_ID, targetCollection = nr.MOLES_COLLECTION) 
    6365        self.assertNotEquals(doc.find(tc.VALID_MOLES_DOC_ID), -1) 
    6466         
    6567    def testGetISO(self): 
    6668        dr = DocumentRetrieve.DocumentRetrieve(tc.VALID_REPOSITORY) 
    67         doc = dr.get(tc.VALID_PROVIDER_ID, tc.ISO_DOC_TYPE, \ 
    68                      tc.VALID_MOLES_DOC_ID, targetCollection = tc.MOLES_COLLECTION) 
     69        doc = dr.get(tc.VALID_PROVIDER_ID, no.ISO_DOC_TYPE, \ 
     70                     tc.VALID_MOLES_DOC_ID, targetCollection = nr.MOLES_COLLECTION) 
    6971        self.assertNotEquals(doc.find(tc.VALID_MOLES_DOC_ID), -1) 
    7072                 
    7173    def testGetISO(self): 
    7274        dr = DocumentRetrieve.DocumentRetrieve(tc.VALID_REPOSITORY) 
    73         doc = dr.get(tc.VALID_PROVIDER_ID, tc.ISO_DOC_TYPE, \ 
    74                      tc.VALID_MOLES_DOC_ID, targetCollection = tc.MOLES_COLLECTION) 
     75        doc = dr.get(tc.VALID_PROVIDER_ID, no.ISO_DOC_TYPE, \ 
     76                     tc.VALID_MOLES_DOC_ID, targetCollection = nr.MOLES_COLLECTION) 
    7577        self.assertNotEquals(doc.find(tc.VALID_MOLES_DOC_ID), -1) 
    7678                 
    7779    def testGetDC(self): 
    7880        dr = DocumentRetrieve.DocumentRetrieve(tc.VALID_REPOSITORY) 
    79         doc = dr.get(tc.VALID_PROVIDER_ID, tc.DC_DOC_TYPE, \ 
    80                      tc.VALID_MOLES_DOC_ID, targetCollection = tc.MOLES_COLLECTION) 
     81        doc = dr.get(tc.VALID_PROVIDER_ID, no.DC_DOC_TYPE, \ 
     82                     tc.VALID_MOLES_DOC_ID, targetCollection = nr.MOLES_COLLECTION) 
    8183        self.assertNotEquals(doc.find(tc.VALID_MOLES_DOC_ID), -1) 
    8284                         
    8385    def testGetNumSim(self): 
    8486        dr = DocumentRetrieve.DocumentRetrieve(tc.VALID_REPOSITORY) 
    85         doc = dr.get(tc.VALID_PROVIDER_ID, tc.NUMSIM_DOC_TYPE, \ 
    86                      tc.VALID_NUMSIM_DOC_ID, targetCollection = tc.NUMSIM_COLLECTION) 
     87        doc = dr.get(tc.VALID_PROVIDER_ID, no.NUMSIM_DOC_TYPE, \ 
     88                     tc.VALID_NUMSIM_DOC_ID, targetCollection = nr.NUMSIM_COLLECTION) 
    8789        self.assertNotEquals(doc.find(tc.VALID_NUMSIM_DOC_ID), -1) 
    8890                         
    8991    def testGetAtom(self): 
    9092        dr = DocumentRetrieve.DocumentRetrieve(tc.VALID_REPOSITORY) 
    91         doc = dr.get(tc.VALID_PROVIDER_ID, tc.ATOM_DOC_TYPE, \ 
    92                      tc.VALID_ATOM_DOC_ID, targetCollection = tc.ATOM_COLLECTION) 
     93        doc = dr.get(tc.VALID_PROVIDER_ID, no.ATOM_DOC_TYPE, \ 
     94                     tc.VALID_ATOM_DOC_ID, targetCollection = nr.ATOM_COLLECTION) 
    9395        self.assertNotEquals(doc.find(tc.VALID_ATOM_DOC_ID), -1) 
     96                         
     97    def testGetInvalidAtom(self): 
     98        dr = DocumentRetrieve.DocumentRetrieve(tc.VALID_REPOSITORY) 
     99        self.assertRaises(ValueError, dr.get, tc.VALID_PROVIDER_ID, no.ATOM_DOC_TYPE, \ 
     100                     tc.VALID_ATOM_DOC_ID + "blah", targetCollection = nr.ATOM_COLLECTION) 
    94101                         
    95102    def testGetAtomBackup(self): 
    96103        dr = DocumentRetrieve.DocumentRetrieve(tc.VALID_REPOSITORY) 
    97         doc = dr.get(tc.VALID_PROVIDER_ID, tc.ATOM_BACKUP_DOC_TYPE, \ 
    98                      tc.VALID_ATOM_DOC_ID, targetCollection = tc.ATOM_BACKUP_COLLECTION) 
     104        doc = dr.get(tc.VALID_PROVIDER_ID, no.ATOM_BACKUP_DOC_TYPE, \ 
     105                     tc.VALID_ATOM_DOC_ID, targetCollection = nr.ATOM_BACKUP_COLLECTION) 
    99106        self.assertNotEquals(doc.find(tc.VALID_ATOM_DOC_ID), -1) 
    100107         
  • exist/trunk/python/ndgUtils/test_ndgObject.py

    r3125 r4167  
    1 import unittest 
     1""" 
     2Test cases for the ndgObject module 
     3 
     4NERC Data Grid Project 
     5""" 
     6__author__ = "B Lawrence" 
     7__date__ = "06/12/07" 
     8__copyright__ = "(C) 2007 STFC & NERC" 
     9__license__ = \ 
     10"""This software may be distributed under the terms of the Q Public  
     11License, version 1.0 or later.""" 
     12__contact__ = "b.n.lawrence@rl.ac.uk" 
     13__revision__ = '$Id: $' 
     14 
     15import unittest, os, logging 
     16import ndgObject as no 
    217from ndgObject import ndgObject 
    318import ConfigParser 
     19import testconstants as tc 
     20 
     21logging.basicConfig(level=logging.DEBUG, 
     22                        format='%(asctime)s %(filename)s:%(lineno)d %(levelname)s %(message)s') 
    423 
    524class TestCase(unittest.TestCase): 
     
    928         
    1029        f='test.config' 
     30        if not os.path.exists(f): 
     31            raise IOError("Error: config file, 'test.config' missing") 
    1132        self.config=ConfigParser.ConfigParser() 
    1233        self.config.read(f) 
     34         
     35    def __assertions(self,uri): 
     36        self.assertEqual(tc.VALID_PROVIDER_ID,uri.repository) 
     37        self.assertEqual(no.NDGB0_DOC_TYPE,uri.schema) 
     38        self.assertEqual(tc.VALID_MOLES_DOC_ID,uri.localID) 
    1339     
    1440    def testURI1(self): 
    1541        ''' Testing the underscore ID type ''' 
    16         uri='badc.nerc.ac.uk__NDG-B0__local1' 
     42        uri= tc.VALID_PROVIDER_ID + '__' + no.NDGB0_DOC_TYPE + '__' + \ 
     43            tc.VALID_MOLES_DOC_ID 
    1744        n=ndgObject(uri) 
    1845        self.__assertions(n) 
    1946         
    20     def __assertions(self,uri): 
    21         self.assertEqual('badc.nerc.ac.uk',uri.repository) 
    22         self.assertEqual('NDG-B0',uri.schema) 
    23         self.assertEqual('local1',uri.localID) 
    24          
    2547    def testURI2(self): 
    2648        ''' Testing the colon form of ID ''' 
    27         uri='badc.nerc.ac.uk:NDG-B0:local1' 
     49        uri=tc.VALID_PROVIDER_ID + ':' + no.NDGB0_DOC_TYPE + ':' + \ 
     50            tc.VALID_MOLES_DOC_ID 
    2851        n=ndgObject(uri) 
    2952        self.__assertions(n) 
     
    3154    def notestURI3(self): 
    3255        ''' Testing the default MOLES form of id ''' 
    33         uri='badc.nerc.ac.uk/local1' 
     56        uri=tc.VALID_PROVIDER_ID + '/' + tc.VALID_MOLES_DOC_ID 
    3457        n=ndgObject(uri) 
    3558        self.__assertions(n) 
     
    3760    def testMakeURL(self): 
    3861        ''' Tests making a url via a configuration file ''' 
    39         uri='badc.nerc.ac.uk__NDG-B0__local1' 
     62        uri=tc.VALID_PROVIDER_ID + '__' + no.NDGB0_DOC_TYPE + '__' + \ 
     63            tc.VALID_MOLES_DOC_ID 
    4064        n=ndgObject(uri) 
    4165        n.schema='DIF' 
    4266        n.setConfig(self.config) 
    43         if n.gettable: 
    44              print n.discoveryURL 
    45              self.assertEqual(n.discoveryURL[0:5],'http:') 
     67        self.assertTrue(n.gettable) 
     68        self.assertEqual(n.discoveryURL,'ndgbeta.badc.rl.ac.uk/view/badc.nerc.ac.uk__DIF__dataent_hadisst?outputSchema=') 
    4669           
    4770    def testStubB(self): 
    4871        ''' Test the url components of a stub-b ''' 
    49         uri='badc.nerc.ac.uk__NDG-B1__dataent_chablis' 
     72        uri=tc.VALID_PROVIDER_ID + '__' + no.NDGB1_DOC_TYPE + '__' + \ 
     73            tc.VALID_MOLES_DOC_ID 
    5074        n=ndgObject(uri,self.config) 
    51         print n.discoveryURL 
    52         print n.URL 
    53         print n.xmlURL 
    54         print n.printableURL 
     75        self.assertEqual(n.discoveryURL, 'ndgbeta.badc.rl.ac.uk/view/badc.nerc.ac.uk__NDG-B1__dataent_hadisst?outputSchema=') 
     76        self.assertEqual(n.URL, tc.VALID_REPOSITORY) 
     77        self.assertEqual(n.xmlURL, tc.VALID_REPOSITORY + '?format=raw') 
     78        self.assertEqual(n.printableURL, tc.VALID_REPOSITORY + '?format=xml') 
     79           
     80    def testAtom(self): 
     81        ''' Test the url components of a stub-b ''' 
     82        uri=tc.VALID_PROVIDER_ID + '__' + no.ATOM_DOC_TYPE + '__' + \ 
     83            tc.VALID_ATOM_DOC_ID 
     84        n=ndgObject(uri,self.config) 
     85        self.assertEqual(n.discoveryURL, 'ndgbeta.badc.rl.ac.uk/view/badc.nerc.ac.uk__ATOM__dataent_11679938403412067?outputSchema=') 
     86        self.assertEqual(n.URL, tc.VALID_REPOSITORY) 
     87        self.assertEqual(n.xmlURL, tc.VALID_REPOSITORY + '?format=raw') 
     88        self.assertEqual(n.printableURL, tc.VALID_REPOSITORY + '?format=xml') 
    5589 
    5690    def testPMLuri(self): 
     
    5892        uri='www.npm.ac.uk__NDG-B1__dataent.692' 
    5993        n=ndgObject(uri,self.config) 
    60         print n.URL 
     94        self.assertEqual(n.URL, tc.VALID_REPOSITORY) 
    6195 
    6296    def testHorribleURI(self): 
    63         ''' tests __ in localID ''' 
     97        ''' tests use of '__' in localID ''' 
    6498        uri='repository__schema__something__else' 
    6599        n=ndgObject(uri) 
  • exist/trunk/python/ndgUtils/test_ndgRetrieve.py

    r4165 r4167  
    1313__revision__ = '$Id: $' 
    1414 
     15import ndgObject as no 
    1516from ndgObject import ndgObject 
    1617from ndgRetrieve import ndgRetrieve 
    1718from xmlHandler2 import xmlHandler 
    1819import ConfigParser 
    19 import unittest, os 
     20import unittest, os, logging 
    2021import testconstants as tc 
     22 
     23logging.basicConfig(level=logging.DEBUG, 
     24                        format='%(asctime)s %(filename)s:%(lineno)d %(levelname)s %(message)s') 
    2125 
    2226class TestCase(unittest.TestCase): 
     
    3034        self.c.read(f) 
    3135             
    32     def testEXIST(self): 
     36    def testRetrieveFromLocalEXIST(self): 
    3337        ''' Test link to local exist via xml rpc. 
    3438                users will need to ensure that the document is actually  
     
    4953        if status: raise ValueError(' NDG-B0 [%s] should not exist [%s]'%(doc,xml) ) 
    5054         
    51     def testXMLdif(self): 
     55    def testRetrieveXMLdif(self): 
    5256        ''' Make sure we can recall at least one DIF properly ''' 
    53         doc= tc.VALID_PROVIDER_ID + "__" + tc.DIF_DOC_TYPE + \ 
     57        doc= tc.VALID_PROVIDER_ID + "__" + no.DIF_DOC_TYPE + \ 
    5458            "__" + tc.VALID_MOLES_DOC_ID 
    5559        uri=ndgObject(doc) 
     
    6771        self.assertEqual(status,1) 
    6872         
    69     def testNumSim(self): 
     73    def testRetrieveNumSim(self): 
    7074        '''Get a NumSim document ''' 
    71         doc= tc.VALID_PROVIDER_ID + "__" + tc.NUMSIM_DOC_TYPE + \ 
     75        doc= tc.VALID_PROVIDER_ID + "__" + no.NUMSIM_DOC_TYPE + \ 
    7276            "__" + tc.VALID_NUMSIM_DOC_ID 
    7377        uri=ndgObject(doc) 
     
    7680        self.assertNotEqual(xml.find(tc.VALID_NUMSIM_DOC_ID), -1) 
    7781         
    78     def testAtom(self): 
     82    def testRetrieveAtom(self): 
    7983        '''Get an atom document ''' 
    80         doc= tc.VALID_PROVIDER_ID + "__" + tc.ATOM_DOC_TYPE + \ 
     84        doc= tc.VALID_PROVIDER_ID + "__" + no.ATOM_DOC_TYPE + \ 
    8185            "__" + tc.VALID_ATOM_DOC_ID 
    8286        uri=ndgObject(doc) 
     
    8589        self.assertNotEqual(xml.find(tc.VALID_ATOM_DOC_ID), -1) 
    8690         
    87     def testAtomBackup(self): 
     91    def testRetrieveInvalidAtom(self): 
     92        '''Get an invalid atom document ''' 
     93        doc= tc.VALID_PROVIDER_ID + "__" + no.ATOM_DOC_TYPE + \ 
     94            "__" + tc.VALID_ATOM_DOC_ID + "blah" 
     95        uri=ndgObject(doc) 
     96        status,xml=ndgRetrieve(uri,self.c, discovery=0) 
     97        self.assertEqual(status,0) 
     98         
     99    def testRetrieveAtomBackup(self): 
    88100        '''Get a backed up atom document ''' 
    89         doc= tc.VALID_PROVIDER_ID + "__" + tc.ATOM_BACKUP_DOC_TYPE + \ 
     101        doc= tc.VALID_PROVIDER_ID + "__" + no.ATOM_BACKUP_DOC_TYPE + \ 
    90102            "__" + tc.VALID_ATOM_DOC_ID 
    91103        uri=ndgObject(doc) 
  • exist/trunk/python/ndgUtils/testconstants.py

    r4165 r4167  
    1515VALID_REPOSITORY = "chinook.badc.rl.ac.uk" 
    1616VALID_PROVIDER_ID = 'badc.nerc.ac.uk' 
    17 MOLES_DOC_TYPE = 'NDG-B0' 
    18 DIF_DOC_TYPE = 'DIF' 
    19 DC_DOC_TYPE = 'DC' 
    20 ISO_DOC_TYPE = 'ISO19139' 
    21 NDGB1_DOC_TYPE = 'NDG-B1' 
    22 NDGB0_DOC_TYPE = 'NDG-B0' 
    23 MDIP_DOC_TYPE = 'MDIP' 
    24 NDGA0_DOC_TYPE = 'NDG-A0' 
    25 NUMSIM_DOC_TYPE = 'NumSim' 
    26 ATOM_DOC_TYPE = 'ATOM' 
    27 ATOM_BACKUP_DOC_TYPE = 'ATOM-BACKUP' 
    2817VALID_MOLES_DOC_ID = 'dataent_hadisst' 
    2918VALID_NDGA_DOC_ID = 'COAPEC_500YrRun_wholerun_annual_atmos' 
    3019VALID_NUMSIM_DOC_ID = 'HadGEM1_CodeBase' 
    3120VALID_ATOM_DOC_ID = 'dataent_11679938403412067' 
    32 MOLES_COLLECTION = '/db/ndg_B_metadata' 
    33 ATOM_COLLECTION = '/db/atoms' 
    34 ATOM_BACKUP_COLLECTION = '/db/atoms_backup' 
    35 NUMSIM_COLLECTION = '/db/NumSim' 
    36 NDGA0_COLLECTION = '/db/ndg_A_metadata' 
    3721         
Note: See TracChangeset for help on using the changeset viewer.