Back to index

plone3  3.1.7
storage.py
Go to the documentation of this file.
00001 from zope.interface import implements
00002 
00003 from persistent import Persistent
00004 from BTrees.OOBTree import OOBTree, OOSet
00005 
00006 from plone.app.redirector.interfaces import IRedirectionStorage
00007 
00008 class RedirectionStorage(Persistent):
00009     """Stores old paths to new paths.
00010 
00011     Note - instead of storing "new paths" it could store object ids or
00012     similar. In general, there is a many-to-one relationship between
00013     "old paths" and "new paths". An "old path" points to exactly one
00014     "new path" (where the object is now to be found), but a "new path"
00015     can be pointed to by multiple different "old paths" (several objects
00016     that used to be distinct are now consolidated into one).
00017 
00018     The following tests (see test_storage.py) demonstrate its usage.
00019 
00020         >>> p = RedirectionStorage()
00021 
00022     Add one redirect
00023 
00024         >>> p.has_path('/foo')
00025         False
00026         >>> p.add('/foo', '/bar')
00027         >>> p.has_path('/foo')
00028         True
00029         >>> p.get('/foo')
00030         '/bar'
00031         >>> p.has_path('/bar')
00032         False
00033         >>> p.redirects('/bar')
00034         ['/foo']
00035 
00036     Note that trailing slashes are ignored:
00037 
00038         >>> p.has_path('/foo/')
00039         True
00040         >>> p.get('/foo/')
00041         '/bar'
00042         >>> p.redirects('/bar/')
00043         ['/foo']
00044 
00045     Circular references are ignored
00046 
00047         >>> p.add('/circle', '/circle')
00048         >>> p.has_path('/circle')
00049         False
00050         >>> p.get('/circle', '_marker_')
00051         '_marker_'
00052         >>> p.redirects('/circle')
00053         []
00054 
00055     Add another redirect
00056 
00057         >>> p.has_path('/baz')
00058         False
00059         >>> p.add('/baz', '/bar')
00060         >>> p.has_path('/baz')
00061         True
00062         >>> p.get('/baz')
00063         '/bar'
00064         >>> sorted(p.redirects('/bar'))
00065         ['/baz', '/foo']
00066 
00067     Update a redirect
00068 
00069         >>> p.add('/foo', '/quux')
00070         >>> p.has_path('/foo')
00071         True
00072         >>> p.get('/foo')
00073         '/quux'
00074         >>> p.redirects('/bar')
00075         ['/baz']
00076         >>> p.redirects('/quux')
00077         ['/foo']
00078 
00079     Remove a redirect
00080 
00081         >>> p.remove('/foo')
00082         >>> p.has_path('/foo')
00083         False
00084         >>> p.get('/foo', default='_notfound_')
00085         '_notfound_'
00086         >>> p.redirects('/quux')
00087         []
00088 
00089     Update a redirect in a chain
00090 
00091         >>> p.add('/fred', '/foo')
00092         >>> p.get('/fred')
00093         '/foo'
00094         >>> sorted(p.redirects('/foo'))
00095         ['/fred']
00096 
00097         >>> p.add('/fred', '/barney')
00098         >>> p.get('/fred')
00099         '/barney'
00100         >>> sorted(p.redirects('/foo'))
00101         []
00102         >>> sorted(p.redirects('/barney'))
00103         ['/fred']
00104 
00105         >>> p.add('/barney', '/wilma')
00106         >>> p.get('/fred')
00107         '/wilma'
00108         >>> p.get('/barney')
00109         '/wilma'
00110         >>> sorted(p.redirects('/wilma'))
00111         ['/barney', '/fred']
00112         >>> sorted(p.redirects('/barney'))
00113         []
00114 
00115     Destroy the target of a redirect
00116 
00117         >>> p.destroy('/wilma')
00118         >>> p.has_path('/barney')
00119         False
00120         >>> p.has_path('/fred')
00121         False
00122         >>> p.redirects('/wilma')
00123         []
00124 
00125     We can get an iterator over all existing paths
00126 
00127         >>> iter(p)
00128         <OO-iterator object at ...>
00129         >>> sorted(p)
00130         ['/baz']
00131 
00132     Now add some more
00133 
00134         >>> p.add('/foo', '/bar')
00135         >>> p.add('/barney', '/wilma')
00136         >>> sorted(p)
00137         ['/barney', '/baz', '/foo']
00138     """
00139 
00140     implements(IRedirectionStorage)
00141 
00142     def __init__(self):
00143         self._paths = OOBTree()
00144         self._rpaths = OOBTree()
00145 
00146     def add(self, old_path, new_path):
00147         old_path = self._canonical(old_path)
00148         new_path = self._canonical(new_path)
00149 
00150         if old_path == new_path:
00151             return
00152 
00153         # Forget any existing reverse paths to old_path
00154         existing_target = self._paths.get(old_path, None)
00155         if existing_target is not None and self._rpaths.has_key(existing_target):
00156             if len(self._rpaths[existing_target]) == 1:
00157                 del self._rpaths[existing_target]
00158             else:
00159                 self._rpaths[existing_target].remove(old_path)
00160 
00161         # Update any references that pointed to old_path
00162         for p in self.redirects(old_path):
00163             self._paths[p] = new_path
00164             self._rpaths.setdefault(new_path, OOSet()).insert(p)
00165 
00166         # Remove reverse paths for old_path
00167         if old_path in self._rpaths:
00168             del self._rpaths[old_path]
00169 
00170         self._paths[old_path] = new_path
00171         self._rpaths.setdefault(new_path, OOSet()).insert(old_path)
00172 
00173     def remove(self, old_path):
00174         old_path = self._canonical(old_path)
00175         new_path = self._paths.get(old_path, None)
00176         if new_path is not None and self._rpaths.has_key(new_path):
00177             if len(self._rpaths[new_path]) == 1:
00178                 del self._rpaths[new_path]
00179             else:
00180                 self._rpaths[new_path].remove(old_path)
00181         del self._paths[old_path]
00182 
00183     def destroy(self, new_path):
00184         new_path = self._canonical(new_path)
00185         for p in self._rpaths.get(new_path, []):
00186             if p in self._paths:
00187                 del self._paths[p]
00188         if self._rpaths.has_key(new_path):
00189             if new_path in self._rpaths:
00190                 del self._rpaths[new_path]
00191 
00192     def has_path(self, old_path):
00193         old_path = self._canonical(old_path)
00194         return bool(self._paths.has_key(old_path))
00195 
00196     def get(self, old_path, default=None):
00197         old_path = self._canonical(old_path)
00198         return self._paths.get(old_path, default)
00199 
00200     def redirects(self, new_path):
00201         new_path = self._canonical(new_path)
00202         return [a for a in self._rpaths.get(new_path, [])]
00203 
00204     def _canonical(self, path):
00205         if path.endswith('/'):
00206             path = path[:-1]
00207         return path
00208 
00209     def __iter__(self):
00210         return iter(self._paths)