Author: ianb
Date: 2004-04-12 21:15:39 -0600 (Mon, 12 Apr 2004)
New Revision: 39
Added:
Wiki/lib/wikiindex.py
Modified:
Wiki/Context/Main.py
Wiki/lib/wiki.py
Wiki/lib/wikiconfig.py
Wiki/lib/wikipage.py
Log:
Added indexing of backlinks
Modified: Wiki/Context/Main.py
===================================================================
--- Wiki/Context/Main.py 2004-04-13 01:42:58 UTC (rev 38)
+++ Wiki/Context/Main.py 2004-04-13 03:15:39 UTC (rev 39)
@@ -39,7 +39,7 @@
def actions(self):
return ['edit', 'preview', 'save', 'cancel', 'history',
- 'externalEdit', 'webdav']
+ 'externalEdit', 'webdav', 'backlinks']
def defaultAction(self):
self.view = 'writeRead'
@@ -75,6 +75,10 @@
self.view = 'writeHistory'
self.titlePrefix = 'History of: '
+ def backlinks(self):
+ self.view = 'writeBacklinks'
+ self.titlePrefix = 'Backlinks to: '
+
def writeContent(self):
if self.view:
getattr(self, self.view)()
@@ -205,6 +209,10 @@
self.link(action='history', unversioned=True),
'History',
self.view == 'writeHistory'))
+ tabs.append(self._tabLink(
+ self.link(action='backlinks', unversioned=True),
+ 'Backlinks',
+ self.view == 'writeBacklinks'))
return tabs
def _tabLink(self, url, text, isViewing, title=''):
@@ -268,3 +276,13 @@
self.write('</table>')
self.write('<a href="%s">View %s</a><br>\n'
% (self.link(), self.page.title))
+
+ def writeBacklinks(self):
+ self.write('<table>\n')
+ for index, page in enumerate(self.page.backlinks):
+ self.write('<tr class="%s"><td><a href="%s">%s</a>'
+ '</td></tr>\n'
+ % (['odd', 'even'][index%2],
+ page.link,
+ page.title))
+ self.write('</table>')
Modified: Wiki/lib/wiki.py
===================================================================
--- Wiki/lib/wiki.py 2004-04-13 01:42:58 UTC (rev 38)
+++ Wiki/lib/wiki.py 2004-04-13 03:15:39 UTC (rev 39)
@@ -2,6 +2,8 @@
import wikipage
import rssobject
import propertymeta
+import threading
+import wikiindex
class GlobalWiki(object):
@@ -15,35 +17,89 @@
self.root = config.basePath
assert os.path.exists(self.root)
self.specialNames = {}
+ self.cachedWikis = {}
+ self.cacheLock = threading.Lock()
def site(self, domain):
"""
Retrieves the Wiki that corresponds to this domain.
Currently by looking for a similarly named subdirectory
- (creating directory if necessary)
+ (creating directory if necessary).
"""
assert '/' not in domain
+ # We go through all this trouble to make sure Wikis are
+ # unique per domain, and that they have a link to their
+ # canonical form if necessary, which is also unique; all
+ # done in a threadsafe manner.
+ try:
+ return self.cachedWikis[domain]
+ except KeyError:
+ self.cacheLock.acquire()
+ try:
+ try:
+ return self.cachedWikis[domain]
+ except KeyError:
+ return self._makeSite(domain)
+ finally:
+ self.cacheLock.release()
+
+ def _makeSite(self, domain):
config = self.config.domainConfig(domain)
- return Wiki(globalWiki=self,
- config=config)
+ canonicalDomain = config.canonicalDomain
+ if domain != canonicalDomain:
+ try:
+ canonical = self.cachedWikis[canonicalDomain]
+ except KeyError:
+ canonical = self._makeSite(canonicalDomain)
+ self.cachedWikis[canonicalDomain] = canonical
+ else:
+ canonical = None
+ result = Wiki(globalWiki=self, config=config,
+ canonical=canonical)
+ self.cachedWikis[domain] = result
+ return result
def addSpecialName(self, specialName):
+ """
+ A 'special name' is a name which isn't in the wiki, but
+ still exists as a page. Like 'recentchanges', which is
+ not a wiki page.
+ """
self.specialNames[specialName] = None
class Wiki(object):
__metaclass__ = propertymeta.MakeProperties
- def __init__(self, globalWiki, config):
+ def __init__(self, globalWiki, config, canonical):
self.config = config
if not os.path.exists(self.config.basePath):
os.mkdir(self.config.basePath)
self.globalWiki = globalWiki
+ self.canonical = canonical
+ if not canonical:
+ if config.rebuildIndex or \
+ wikiindex.WikiIndex.exists(self.config.basePath):
+ needRebuild = True
+ else:
+ needRebuild = False
+ self.index = wikiindex.WikiIndex(self.config.basePath)
+ if needRebuild:
+ self._rebuildIndex()
+ else:
+ self.index = canonical.index
def page(self, name, version=None):
+ """
+ Returns a page by the given name, with the given version
+ (None == current version)
+ """
return wikipage.WikiPage(self, self.config.basePath, name, version=version)
def linkTo(self, pageName):
+ """
+ Returns the href to refer to pageName
+ """
if isinstance(pageName, wikipage.WikiPage):
pageName = pageName.name
return self.config.baseHref + pageName
@@ -52,14 +108,19 @@
return os.path.join(self.config.basePath, filename + '.txt')
def exists(self, name):
+ """
+ True if wiki page by name exists.
+ """
if self.globalWiki.specialNames.has_key(name):
return True
else:
return os.path.exists(self.filenameForName(name))
def search(self, text):
- """Search titles and bodies of pages for ``text``, returning list
- of pages"""
+ """
+ Search titles and bodies of pages for ``text``, returning list
+ of pages
+ """
return [page for page in self.allPages()
if page.searchMatches(text)]
@@ -81,6 +142,10 @@
if filename.endswith('.txt')]
def notifyChange(self, page):
+ """
+ Called by the page everytime it is changed, so global
+ indexing and updating can occur.
+ """
rss = self.rss
item = rssobject.RSSItem(
title=page.title,
@@ -90,8 +155,20 @@
)
rss.addItem(item, 10)
rss.writeText()
+ self.index.setLinks(page.name, page.wikiLinks())
+ def backlinks(self, page):
+ return [self.page(name)
+ for name in self.index.backlinks(page.name)]
+
+ def _rebuildIndex(self):
+ for page in self.allPages():
+ self.index.setLinks(page.name, page.wikiLinks())
+
def rss__get(self):
+ """
+ Returns the rssobject.RSS object.
+ """
rss = rssobject.RSS(self.config.rssFilename)
for attribute in rssobject.rssAttributes:
value = self.config.get('rss.%s' % attribute)
Modified: Wiki/lib/wikiconfig.py
===================================================================
--- Wiki/lib/wikiconfig.py 2004-04-13 01:42:58 UTC (rev 38)
+++ Wiki/lib/wikiconfig.py 2004-04-13 03:15:39 UTC (rev 39)
@@ -90,6 +90,10 @@
def rssFilename__set(self, value):
self.set('rsspath', value)
+ def rebuildIndex__get(self):
+ return self._cascadeGet('rebuildindex', type='getboolean',
+ default=False)
+
def normalizeHref(v):
if not v.endswith('/'):
return v + '/'
Added: Wiki/lib/wikiindex.py
===================================================================
--- Wiki/lib/wikiindex.py 2004-04-13 01:42:58 UTC (rev 38)
+++ Wiki/lib/wikiindex.py 2004-04-13 03:15:39 UTC (rev 39)
@@ -0,0 +1,85 @@
+import shelve
+import os
+import sys
+import atexit
+
+_shelvesToClose = []
+
+def _closeHandler():
+ for db in _shelvesToClose:
+ db.close()
+
+atexit.register(_closeHandler)
+
+class WikiIndex(object):
+
+ _dbName = 'index.db'
+
+ def __init__(self, dir):
+ self.dir = dir
+ self.filename = os.path.join(self.dir, self._dbName)
+ self.store = shelve.open(self.filename)
+
+ def exists(cls, dir):
+ return os.path.exists(os.path.join(dir, self._dbName))
+ exists = classmethod(exists)
+
+ def backlinks(self, pageName):
+ """
+ Returns a list of pages that link to pageName
+ """
+ return self._getLinks('backlink', pageName)
+
+ def forwardLinks(self, pageName):
+ """
+ Returns a list of pages to which pageName links
+ """
+ return self._getLinks('forward', pageName)
+
+ def _setBacklinks(self, pageName, links):
+ self._setLinks('backlink', pageName, links)
+
+ def _setForwardLinks(self, pageName, links):
+ self._setLinks('forward', pageName, links)
+
+ def setLinks(self, pageName, links):
+ """
+ Indexes all the backlinks for a page; also indexes forward
+ links, so that we can tell when a backlink needs to be
+ removed.
+ """
+ oldLinks = self.forwardLinks(pageName)
+ toRemove = []
+ toAdd = []
+ links.sort()
+ while links and oldLinks:
+ if links[0] > oldLinks[0]:
+ toRemove.append(oldLinks.pop(0))
+ elif links[0] < oldLinks[0]:
+ toAdd.append(links.pop(0))
+ else:
+ oldLinks.pop(0)
+ links.pop(0)
+ toRemove.extend(oldLinks)
+ toAdd.extend(links)
+
+ for link in toRemove:
+ prev = self.backlinks(link)
+ prev.remove(link)
+ self._setBacklinks(link, prev)
+ for link in toAdd:
+ prev = self.backlinks(link)
+ prev.append(pageName)
+ prev.sort()
+ self._setBacklinks(link, prev)
+
+ self._setForwardLinks(pageName, links)
+
+ def _getLinks(self, type, pageName):
+ result = self.store.get('%s.%s' % (type, pageName), '')
+ result = filter(None, result.split(','))
+ return result
+
+ def _setLinks(self, type, pageName, links):
+ links = ','.join(links)
+ self.store['%s.%s' % (type, pageName)] = links
Modified: Wiki/lib/wikipage.py
===================================================================
--- Wiki/lib/wikipage.py 2004-04-13 01:42:58 UTC (rev 38)
+++ Wiki/lib/wikipage.py 2004-04-13 03:15:39 UTC (rev 39)
@@ -68,20 +68,24 @@
def html__get(self):
"""Returns text of HTML for page (HTML fragment only)"""
if self.exists():
- filename = self.basePath + ".html"
- if not os.path.exists(filename):
- f = open(filename, 'w')
- html = self._convertText(self.text)
- f.write(html)
- f.close()
- return self._subWikiLinks(html)
- else:
- html = open(filename).read()
- html = self._subWikiLinks(html)
- return html
+ return self._subWikiLinks(self._rawHTML())
else:
return 'This page has not yet been created.'
+ def _rawHTML(self):
+ if not self.exists():
+ return ''
+ filename = self.basePath + ".html"
+ if not os.path.exists(filename):
+ f = open(filename, 'w')
+ html = self._convertText(self.text)
+ f.write(html)
+ f.close()
+ return html
+ else:
+ html = open(filename).read()
+ return html
+
def readOnly__get(self):
if self.version:
return True
@@ -97,6 +101,16 @@
def _subWikiLinks(self, text):
return self._wikiLinkRE.sub(self._subLink, ' %s ' % text)
+ def wikiLinks(self):
+ """
+ The names of all the wiki pages that this page links to.
+ """
+ return [match.group(2)
+ for match in self._wikiLinkRE.finditer(self._rawHTML())]
+
+ def backlinks__get(self):
+ return self.wiki.backlinks(self)
+
def _subLink(self, match):
if self.wiki.exists(match.group(2)):
return match.group(1) + self.wiki.linkTo(match.group(2)) + match.group(3) + match.group(4) + match.group(5)
|