Logo Search packages:      
Sourcecode: zim version File versions  Download package


# -*- coding: utf-8 -*-

# Copyright 2008 Jaap Karssenberg <pardus@cpan.org>

'''Base class for store modules.

This module contains a base class for store modules. It implements
some common methods and provides API documentation for the store

Each store module should implement a class named "Store" which
inherits from StoreClass. All methods marked with "ABSTRACT" need to
be implemented in the sub class. When called directly they will raise
a NotImplementedError. Overloading other methods is optional.

=== Storage Model ===

Stores handle content in terms of Page objects. How the data that is
managed by the store is mapped to pages is up to the store implementation.
For example in the default store each page is mapped to a text file,
but there can also be store impementations that store many pages in the
same file, or that use for example a database. The store is however
expected to be consistent. So when a page is stored under a specific name
it should also be retrievable under that name.

Pages can be stored in a hierarchic way where each page can have sub-pages.
Or, in other terms, each page has a like names namespace that can store
sub pages. In the default store this structure is mapped to a directory
structure where for each page there can be a like named directory which
contains the files used to store sub-pages. The full page name for a page
consists of the names of all it's parents plus it's own base name seperated
with the ':' character. It is advised that each page should have a unique
name. Symbolic links or aliases for pages should be handled on a different
level. In the store interface page names are always assumed to be case
sensitive. However the store is allowed to be not case sensitive if the storage
backend does not support this (e.g. a file system that is not case sensitive).

The store exposes it's content using Page objects and lists of Page objects.
Each page object has two boolean attributes 'hascontent' and 'haschildren'.
Typically in a page listing at least one of these attributes should be true,
as a page either has content of it's own, or is used as a container for
sub-pages, or both. However both attributed can be False for new pages, or
for pages that have just been deleted.

The index will cache page listings in order to speed up the performance,
so it should not be necessary to do speed optializations in the store lookups.
However for eficient caching, store objects should implement the
'get_pagelist_indexkey()' and 'get_page_indexkey()' methods.

The notebook will use Path objects when requesting a specific page. These
paths just map to a specific page name but do not contain any information
about the actual existence of the page etc.

If a non-exising page is requested the store should check if we are allowed
to create the page. If so, a new page object should be returned, but actually
creating the page can be delayed untill content is stored in it. Creating
the page also implicitly creates all of it's parents page, since it should
be visible in the hierarchy of page listings. If we are not allowed to create
the page (e.g. in case of a read-only notebook) no page object should be

If a page list for a non-existing path is requested, the store can just
return an empty list.

from zim.fs import *
from zim.parsing import is_url_re

def get_store(name):
      '''Returns the module object for a specific store type.'''
      # __import__ has some quirks, soo the reference manual
      mod = __import__('zim.stores.'+name)
      mod = getattr(mod, 'stores')
      mod = getattr(mod, name)
      return mod

def encode_filename(pagename):
      '''Encodes a pagename to a filename. Namespaces are mapped to directories.
      Returns basename without extension.
      return pagename.replace(':', '/').replace(' ', '_')

def decode_filename(filename):
      '''Decodes a file basename to a pagename'''
      return filename.replace('/', ':').replace('_', ' ')

class StoreClass():

      def __init__(self, notebook, path):
            '''Constructor for stores.
            At least pass a notebook and the path for our namespace.
            self.notebook = notebook
            self.namespace = path

      def get_page(self, path):
            '''ABSTRACT METHOD, must be implemented in all sub-classes.

            Return a new Page object for a path.
            raise NotImplementedError

      def get_pagelist(self, path):
            '''ABSTRACT METHOD, must be implemented in all sub-classes.

            Should return a list (or iterator) of page objects below a specific
            path. Used by the index to recursively find all pages in the store.
            raise NotImplementedError

      def store_page(self, page):
            '''ABSTRACT METHOD, must be implemented in all sub-classes.

            Store a page in the backend storage.
            raise NotImplementedError

      def move_page(self, path, newpath):
            '''ABSTRACT METHOD, must be implemented in sub-class if store is

            Move content from "oldpath" to "newpath". If oldpath is a Page
            object this should result in 'page.hascontent' being False if

            Raises an error if path does not exist, or if newpath already exists.
            raise NotImplementedError

      def delete_page(self, path):
            '''ABSTRACT METHOD, must be implemented in sub-class if store is

            Deletes a page. If path is a Page object this should result
            in 'page.hascontent' being False if succesfull.

            Returns False if page did not exist in the first place, True otherwise.
            raise NotImplementedError

      def page_exists(self, path):
            '''ABSTRACT METHOD, must be implemented in sub-class.

            Should return boolean whether a page exists or not. Differs from
            page.hascontent because this method should only look at what is stored
            raise NotImplementedError

      def get_pagelist_indexkey(self, path):
            '''This method should return a key that can be checked by the index to
            determine if a list of (sub-)pages should be indexed again. A typical
            implementation would be to return the modification time of the directory
            where the pages are stored. The default in the base class returns None,
            forcing the index to always re-index the page. This is not very
            efficient and should be overloaded by the store.
            return None

      def get_page_indexkey(self, path):
            '''Like get_pagelist_indexkey() but used to decide whether page contents
            should be indexed or not.
            return None

      def store_has_dir(self):
            '''Returns True if we have a directory attribute 'dir'.
            Auto-vivicates the dir based on namespace if needed.
            Intended to be used in an 'assert' statement by subclasses that
            require a directory to store their content.
            if hasattr(self, 'dir') and not self.dir is None:
                  return isinstance(self.dir, Dir)
            elif hasattr(self.notebook, 'dir'):
                  path = self.namespace.name.replace(':', '/')
                  self.dir = Dir([self.notebook.dir, path])
                  return True
                  return False

      def store_has_file(self):
            '''Returns True if we have a file attribute 'file'.
            If we are the toplevel namespace we can take a file source set for
            the whole notebook.
            Intended to be used in an 'assert' statement by subclasses that
            require a file to read their content from.
            if hasattr(self, 'file') and not self.file is None:
                  return isinstance(self.file, File)
            elif hasattr(self.notebook, 'file') and self.namespace.isroot:
                  self.file = self.notebook.file
                  return isinstance(self.file, File)
                  return False

      def get_attachments_dir(self, path):
            '''Returns a Dir object for storing attachements for 'path'.
            Assumes the store has a directory set already and aplies the
            default heuristic for mapping page names to file names.
            Sub-classes that do not have a directory or want a different
            layout need to subclass this method.
            # TODO merge with _get_dir and _get_file in stores/files.py
            assert self.dir, 'Stores without a dir attribute need to overload this method'
            if path == self.namespace:
                  return self.dir
                  name = path.relname(self.namespace)
                  dirpath = encode_filename(name)
                  return Dir([self.dir, dirpath])

      def walk(self, path=None):
            '''Generator walking all pages under this store. This is intended
            for some low level operations. From the application you typically
            want to use either notebook.walk() or index.walk() which traverse
            all stores.
            if path is None:
                  path = self.namespace

            for page in self.get_pagelist(path):
                  yield page
                  for child in self.walk(page): # recurs
                        yield child

Generated by  Doxygen 1.6.0   Back to index