# # This file is part of Documancer (http://documancer.sf.net) # # Copyright (C) 2002-2005 Vaclav Slavik # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Id: book.py,v 1.15 2005/02/06 11:39:21 vaclavslavik Exp $ # # Book data structures # import os, os.path, re, types, glob, xml.dom.minidom, copy try: import xml.dom.ext HAVE_PYXML=1 except ImportError: HAVE_PYXML=0 import utils #import indexer, server, providers - done at the end of the file ATTR_INDEXED = 'indexed' ATTR_CACHEDIR = 'cachedir' # XML I/O helper functions: XML_NS = 'http://documancer.sourceforge.net/bookfile-0.2.4' def xmlMakeNode(doc, parent, name, text=None): e = doc.createElementNS(XML_NS, name) if parent == None: doc.appendChild(e) else: parent.appendChild(e) if text != None: t = doc.createTextNode(text) e.appendChild(t) return e def xmlGetNode(parent, name): return parent.getElementsByTagNameNS(XML_NS, name)[0] def xmlGetText(nodep): rc = '' for node in nodep.childNodes: if node.nodeType == node.TEXT_NODE: rc = rc + node.data return rc class ContentsNode: """Book contents""" def __init__(self, parent, name, url): self.children = None self.name = name self.url = url self.parent = parent def getChildren(self): if self.children == None: self.children = self._expand() return self.children def hasChildren(self): if self.children == None: return self._hasChildren() else: return len(self.children) > 0 def _expand(self): """Override this in derived class to return list to be stored as self.children""" return [] def _hasChildren(self): """Override to return if the node has children. This is an estimate only and is called only before _expand() filled self.children. It should return 0 if it is certain that the node has no children and 1 if it is _possible_ that _expand() will return non-empty list.""" return 0 class BookData: """Data used to construct a book.""" def __init__(self, title, provider, attrs): """Initializes book record. 'provider' is string identifying data provider plugin, 'attrs' is dictionary of provider-specific settings such as filename or source URL.""" self.title = title self.provider = provider self.attr = attrs class Book: def __init__(self, data): """Initializes book from BookData record.""" self.definitionFile = None # =default location self.title = data.title self.provider = data.provider self.attr = data.attr self.contents = None if ATTR_INDEXED not in self.attr: self.attr[ATTR_INDEXED] = '0' self.bookmarks = {} self.getProviderObj().initForBook(self) def setData(self, data): """Updates book from book data.""" self.title = data.title self.provider = data.provider for a in data.attr: self.setAttr(a, data.attr[a]) for a in [x for x in self.attr if x not in data.attr]: del self.attr[a] def getData(self): """Gets book's definition data.""" return BookData(self.title, self.provider, copy.deepcopy(self.attr)) def getDefinitionFile(self): """Returns name of XML file with book definition.""" if self.definitionFile == None: return '%s/%s.xml' % (utils.getBooksDir(), utils.bookNameToFileName(self.title)) else: return self.definitionFile def save(self): """Saves the book into XML file.""" doc = xml.dom.minidom.Document() root = xmlMakeNode(doc, None, "book") xmlMakeNode(doc, root, "title", self.title) xmlMakeNode(doc, root, "provider", self.provider) attrs = xmlMakeNode(doc, root, "attributes") for a in self.attr: if self.attr[a] == '': val = None else: val = self.attr[a] attr = xmlMakeNode(doc, attrs, "attr", val) attr.setAttribute("name", a) if len(self.bookmarks) > 0: bookmarks = xmlMakeNode(doc, root, "bookmarks") for b in self.bookmarks: bookmark = xmlMakeNode(doc, bookmarks, "bookmark", b) bookmark.setAttribute("url", self.bookmarks[b]) if HAVE_PYXML: xml.dom.ext.PrettyPrint(doc, open(self.getDefinitionFile(), "wt")) else: # keep minidom's toxml() happy, PyXML doesn't need this hack: root.setAttribute("xmlns", XML_NS) open(self.getDefinitionFile(), "wt").write(doc.toxml()) def getProviderObj(self): return providers.providers[self.provider] def setAttr(self, attr, value): modified = ((attr not in self.attr) or (self.attr[attr] != value)) if modified: self.attr[attr] = value if attr == ATTR_INDEXED and value == '0': import indexer indexer.disableIndex(self) import cache cache.get(self).remakeAll() def getAttr(self, attr): return self.attr[attr] def hasAttr(self, attr): return attr in self.attr def addBookmark(self, name, url): # remove http://localhost:XXXX/ prefix, so that the bookmark # is independent of used port number: url2 = re.sub("^http://localhost:%i" % utils.getServerPort(), "", url) self.bookmarks[name] = url2 def delBookmark(self, name): del self.bookmarks[name] def getBookmarks(self): """Returns tuple (keys,dict), where 'keys' is sorted list of bookmark titles and 'dict' is dictionary with URLs, keyed by the titles.""" port = utils.getServerPort() keys = self.bookmarks.keys() keys.sort() out = {} for k in keys: out[k] = "http://localhost:%i%s" % (port, self.bookmarks[k]) return (keys, out) def makeFullURL(self, url): """Translates relative (to the book) URL to absolute HTTP URL for the background server.""" return "http://localhost:%i/%s/%s" % \ (utils.getServerPort(), utils.mangleBookName(self.title), url) def getHomeURL(self): """Returns URL that can be passed to the browser for root page.""" return self.makeFullURL(self.getProviderObj().getHomeURL(self)) def getContents(self): """Returns book contents object""" if self.contents == None: self.contents = self.getProviderObj().getContents(self) return self.contents def _deleteBook(self): """Removes all traces of the book from temporary dirs.""" import cache cache.cache.delete(self) if self.definitionFile == None: os.remove(self.getDefinitionFile()) def loadBook(file, externalFile=0): doc = xml.dom.minidom.parse(file) root = xmlGetNode(doc, 'book') title = xmlGetText(xmlGetNode(root, 'title')) provider = xmlGetText(xmlGetNode(root, 'provider')) attr = {} bookmarks = {} for node in xmlGetNode(root, 'attributes').getElementsByTagNameNS( XML_NS, 'attr'): aname = node.getAttribute('name') avalue = xmlGetText(node) attr[aname] = avalue book = Book(BookData(title, provider, attr)) nodebookm = root.getElementsByTagNameNS(XML_NS, 'bookmarks') if len(nodebookm) == 1: for node in nodebookm[0].getElementsByTagNameNS(XML_NS, 'bookmark'): burl = node.getAttribute('url') btitle = xmlGetText(node) book.bookmarks[btitle] = burl if externalFile: book.definitionFile = file return book books = {} additionalBooks = [] # in addition to these in ~/.documancer/books/ def loadBooks(): global books books = {} for file in glob.glob('%s/*.xml' % utils.getBooksDir()): book = loadBook(file) books[book.title] = book for file in additionalBooks: book = loadBook(file, externalFile=1) books[book.title] = book def saveBooks(): for b in books.values(): b.save() def deleteBook(b): books[b]._deleteBook() del books[b] # NB: done here because of cross-importing: we need to have ContentsNode class # available at the time providers module is imported import providers