* looking for arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-460 to compare with
* comparing to arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-460
M  MoinMoin/logfile/eventlog.py
M  MoinMoin/logfile/editlog.py
M  MoinMoin/server/standalone.py
M  MoinMoin/theme/__init__.py
M  MoinMoin/Page.py
M  MoinMoin/PageEditor.py
M  MoinMoin/action/AttachFile.py
M  MoinMoin/caching.py
M  MoinMoin/request.py
M  MoinMoin/wikiutil.py

* modified files

--- orig/MoinMoin/Page.py
+++ mod/MoinMoin/Page.py
@@ -7,7 +7,7 @@
 """
 
 import StringIO, os, re, urllib, os.path, random, codecs
-from MoinMoin import config, caching, user, util, wikiutil
+from MoinMoin import config, caching, user, util, wikiutil, error
 from MoinMoin.logfile import eventlog
 from MoinMoin.util import filesys, web
 
@@ -20,11 +20,20 @@
 header_re = re.compile(r'(^#+.*(?:\n\s*)+)+', re.UNICODE | re.MULTILINE)
 
 
-class Page:
+# Page factory - return pages from request._pages dict
+def Page(request, page_name, **kw):
+    # Note - ignore kw on page creation, its broken design
+    ## return _Page(request, page_name)
+    if not page_name in request._pages:
+        page = _Page(request, page_name)
+        request._pages[page_name] = page
+    return request._pages[page_name]
+    
+class _Page:
     """Page - Manage an (immutable) page associated with a WikiName.
        To change a page's content, use the PageEditor class.
     """
-
+    
     def __init__(self, request, page_name, **keywords):
         """
         Create page object.
@@ -45,13 +54,29 @@
         self._raw_body_modified = 0
         self.hilite_re = None
         self.language = None
-        
+
         if keywords.has_key('formatter'):
             self.formatter = keywords.get('formatter')
             self.default_formatter = 0
         else:
             self.default_formatter = 1
 
+        # Page info is filled with data as data is requested
+        self._urlName = None
+        self._storageName = None
+        self._pageinfo = None
+
+        ## request.log('Created page %s' % repr(page_name))
+
+    def storageName(self):
+        if self._storageName is None:
+            self._storageName = wikiutil.quoteWikinameFS(self.page_name)
+        return self._storageName
+
+    def urlName(self):
+        if self._urlName is None:
+            self._urlName = wikiutil.quoteWikinameURL(self.page_name)
+        return self._urlName
 
     def split_title(self, request, force=0):
         """
@@ -74,37 +99,9 @@
         splitted = SPLIT_RE.sub(r'\1 \2', self.page_name)
         return splitted
 
-    def get_rev(self, pagedir=None, rev=0):
-        """ 
-        get a revision of the page in this path
-        @param pagedir: the path to the pagedir
-        @param rev: int revision to get (default is 0 and means the current
-                    revision (in this case, the real revint is returned)
-        @return: (str pagefilename, int realrevint, bool exists)
-        """
-        if pagedir is None:
-            pagedir = self.getPagePath(check_create=0)
-            
-        if rev == 0:
-            revfilename = os.path.join(pagedir, 'current')
-            try:
-                revfile = open(revfilename)
-                revstr = revfile.read().strip()
-                revfile.close()
-            except:
-                revstr = '99999999' # XXX
-            rev = int(revstr)
-        else:
-            revstr = '%08d' % rev
-        
-        pagefile = os.path.join(pagedir, 'revisions', revstr)
-        exists = os.path.exists(pagefile)
-        return pagefile, rev, exists
-
     def current_rev(self):
-        pagefile, rev, exists = self.get_rev()
-        return rev
-
+        return self.getInfo().get('revision')
+        
     def get_real_rev(self):
         """Returns the real revision number of this page. A rev=0 is
         translated to the current revision.
@@ -116,85 +113,199 @@
             return self.current_rev()
         return self.rev
 
-    def getPagePath(self, *args, **kw):
-        """
-        Get full path to a page-specific storage area. `args` can
-        contain additional path components that are added to the base path.
+    def hasRevision(self, revision):
+        """ Does page has revision number? """
+        info = self.getInfo()
+        current = info['revision']
+        if revision == current:
+            return info.get('textfile') is not None
+        elif revision < current:
+            filename = '%08d' % revision
+            path = os.path.join(info['path'], 'revisions', filename)
+            return os.path.exists(path)
 
-        @param args: additional path components
-        @keyword force_pagedir: force using a specific pagedir, default 'auto'
-                                'auto' = automatically choose page dir
-                                'underlay' = use underlay page dir
-                                'standard' = use standard page dir
-        @keyword check_create: if true, ensures that the path requested really exists
-                               (if it doesn't, create all directories automatically).
-                               (default true)
-        @keyword isfile: is the last component in args a filename? (default is false)
-        @rtype: string
-        @return: the full path to the storage area
-        """
-        force_pagedir = kw.get('force_pagedir', 'auto')
-        check_create = kw.get('check_create', 1)
-        qpagename = wikiutil.quoteWikinameFS(self.page_name)
-        data_dir = self.cfg.data_dir
-        # underlay is used to store system and help pages in a separate place
-        underlay_dir = self.cfg.data_underlay_dir
-        if underlay_dir:
-            underlaypath = os.path.join(underlay_dir, "pages", qpagename)
-        else:
-            force_pagedir = 'standard'
+        # No such revision
+        return False
         
-        # self is a NORMAL page
-        if not self is self.request.rootpage:
-            kw['check_create'] = 0 # we don't want to create empty page dirs:
-            path = self.request.rootpage.getPagePath("pages", qpagename, **kw)
-            if force_pagedir == 'auto': 
-                pagefile, rev, exists = self.get_rev(path)
-                if not exists:
-                    pagefile, rev, exists = self.get_rev(underlaypath)
-                    if exists:                         
-                        path = underlaypath
-            elif force_pagedir == 'underlay':
-                path = underlaypath
-            # no need to check 'standard' case, we just use path in that case!
+    def getRevision(self, revision):
+        """ Return page text as of revision, or None """
+        if self.hasRevision(revision):
+            info = self.getInfo()
+            textfile = info.get('textfile')
+            if textfile is None:
+                textfile = '%08d' % revision
+                textfile = os.path.join(info['path'], 'revisions', textfile)
+            return self.readText(textfile)
+        # No such revision or revision file missing
+        return None
+
+    def readTextFile(self, path):
+        """ Read text file at path """
+        try:
+            file = codecs.open(path, 'rb', config.charset)
+        except IOError, err:
+            import errno
+            if er.errno == errno.ENOENT:
+                # just doesn't exist, return empty text (note that we
+                # never store empty pages, so this is detectable and also
+                # safe when passed to a function expecting a string)
+                return ""
+            else:
+                msg = '''
+Could not read text file in %(path)s because of unexpected error: %(class)s: %(error)s
+
+Make sure page directories have correct ownership and permissions.
+''' % {'path': path, 'class': err.__class__.__name__, 'error': str(err),}
+                raise error.InternalError(msg)
+
+        # read file content and make sure it is closed properly
+        try:
+            text = file.read()
+            text = self.decodeTextMimeType(text)
+        finally:
+            file.close()
+
+        return text
         
-        # self is rootpage
-        else:
-            # our current rootpage is not a toplevel, but under another page
-            if self.page_name:
-                # this assumes flat storage of pages and sub pages on same level
-                path = os.path.join(data_dir, "pages", qpagename)
-                if force_pagedir == 'auto':
-                    pagefile, rev, exists = self.get_rev(path)
-                    if not exists:
-                        pagefile, rev, exists = self.get_rev(underlaypath)
-                        if exists:                         
-                            path = underlaypath
-                elif force_pagedir == 'underlay':
-                    path = underlaypath
-                # no need to check 'standard' case, we just use path in that case!
-            
-            # our current rootpage is THE virtual rootpage, really at top of all
+    def getInfo(self):
+        """ Return a dictionary with page information """
+        if self._pageinfo is None:
+            if self.page_name == u'':
+                # The root page of the wiki, create virtual info
+                info = {'path': self.cfg.data_dir,}
             else:
-                # this is the location of the virtual root page
-                path = data_dir
-                # 'auto' doesn't make sense here. maybe not even 'underlay':
-                if force_pagedir == 'underlay':
-                    path = underlay_dir
-                # no need to check 'standard' case, we just use path in that case!
+                # Check where page live. First try at the data dir
+                name = self.storageName()
+                info = self.getInfoForDomain(domain='standard',
+                                             name=name)
+                if not info.get('textfile'):
+                    # Try the underlay directory
+                    info = self.getInfoForDomain(domain='underlay',
+                                                 name=name)
+            self._pageinfo = info
+
+        return self._pageinfo
                 
-        fullpath = os.path.join(*((path,) + args))
+    def getInfoForDomain(self, domain, name):
+        """ Return information at specific domain """
+        if domain == 'standard':
+            dir = self.cfg.data_dir
+        elif domain == 'underlay' and self.cfg.data_underlay_dir:
+            dir = self.cfg.data_underlay_dir
+        else:
+            return {}
+
+        info = {}
+        # Check if page directory exists
+        path = os.path.join(dir, 'pages', name)
+        if os.path.exists(path):
+            info['domain'] = domain
+            info['path'] = path
+            # Try to get the current revision
+            current = os.path.join(path, 'current')
+            try:
+                f = file(current)
+                revision = f.read().strip()
+                f.close()
+                info['revision'] = int(revision)
+                # Check if revision exists, or its a deleted page
+                textfile = os.path.join(path, 'revisions', revision)
+                if os.path.exists(textfile):
+                    info['textfile'] = textfile
+            except:
+                # Page is deleted or broken
+                pass
+
+        return info
+    
+    def _makeDirectory(self, path):
+        """ Wraper around mkdir that raise fatal errors
+
+        This method should be used only by Page, as its immutable.
+
+        @param path: path to create
+        """
+        try:
+            os.mkdir(path, 0777 & config.umask)
+        except (IOError, OSError), err:
+            # If the directory already exists, ignore the error
+            if not os.path.isdir(path):
+                msg = '''
+Could not create page sub directory at %(path)s because of %(class)s:
+%(error)s.
+
+Try to set permissions on the wiki directory again. The directory and
+all its subdirectories and files should be readable, writable and
+executable by the web server user and group.
+''' % {'path': childpath, 'class': err.__class__.__name__, 'error': str(err),}
+
+                raise error.InternalError(msg)
+                    
+    def _getChildPath(self, child, repair=0):
+        """ The central point to get page childern paths
+
+        This method should not be used out of Page. Use the convinience
+        methods: getAttachmentPath, getCachePath etc.
         
-        if check_create:
-            if kw.get('isfile', 0):
-                dirname, filename = os.path.split(fullpath)
+        @param child: one of page children: pages, revisions,
+            attachments etc (string).
+        @param repair: if true, create missing directories
+        @rtype: unicode
+        @return: path to child or None if there is not page dir
+
+        """
+        info = self.getInfo()
+        # Return none for non existing page directory
+        pagepath = info.get('path')
+        if not pagepath:
+            return None
+
+        # Get value for child key on first call
+        if not info.has_key(child):
+            # Check if child exists
+            childpath = os.path.join(pagepath, child)
+            if os.path.exists(childpath):
+                info[child] = childpath
+            elif repair:
+                # Repair missing directories
+                self._makeDirectory(childpath)
+                info[child] = childpath
             else:
-                dirname = fullpath
-            if not os.path.exists(dirname):
-                filesys.makeDirs(dirname)
+                info[child] = None
+            
+        return info[child]
+
+    # Get methods ------------------------------------------------------
+    
+    # Public get methods that hides the internal structure of the
+    # page. The caller does not have to know the names of the children.
+
+    # TODO: all these methods are wrong. Clients should not know the
+    # page path at all. They should get objects, for example,
+    # getEventLogPath should be called getEventLog and return the page
+    # event log object. This will require larger refactoring.
+    
+    def getEditLockPath(self):
+        return self._getChildPath('edit-lock')
+
+    def getEventLogPath(self):
+        return self._getChildPath('event-log')
+
+    def getEditLogPath(self):
+        return self._getChildPath('edit-log')
+
+    def getAttachmentPath(self, repair=0):
+        return self._getChildPath('attachments', repair=repair)
+    
+    def getPagesPath(self, repair=0):
+        return self._getChildPath('pages', repair=repair)
         
-        return fullpath
+    def getCachePath(self, repair=1):
+        return self._getChildPath('cache', repair=repair)
 
+    def getTempPath(self, repair=1):
+        return self._getChildPath('temp', repair=repair)
+        
     def _text_filename(self, **kw):
         """
         The name of the page file, possibly of an older page.
@@ -205,12 +316,8 @@
         """
         if hasattr(self, '_text_filename_force'):
             return self._text_filename_force
-        rev = kw.get('rev', 0)
-        if not rev and self.rev:
-            rev = self.rev
-        fname, rev, exists = self.get_rev(None, rev)
-        return fname
-
+        return self.getInfo().get('textfile')
+    
     def _tmp_filename(self):
         """
         The name of the temporary file used while saving.
@@ -218,20 +325,27 @@
         @rtype: string
         @return: temporary filename (complete path + filename)
         """
-        rnd = random.randint(0,1000000000)
-        tmpname = os.path.join(self.cfg.data_dir, ('#%s.%d#' % (wikiutil.quoteWikinameFS(self.page_name), rnd)))
-        return tmpname
+        tempdir = self.getTempPath()
+        if not tempdir:
+            return None
+        
+        name = str(long(time.time() * (10**6)))
+        name = os.path.join(tempdir, name)
+        return name
 
     # XXX TODO clean up the mess, rewrite _last_edited, last_edit, lastEditInfo for new logs,
     # XXX TODO do not use mtime() calls any more
     def _last_edited(self, request):
         from MoinMoin.logfile import editlog
+        log = None
         try:
-            logfile = editlog.EditLog(request, self.getPagePath('edit-log', check_create=0, isfile=1))
-            logfile.to_end()
-            log = logfile.previous()
+            logpath = self.getEditLogPath()
+            if logpath is not None:
+                logfile = editlog.EditLog(request, logpath)
+                logfile.to_end()
+                log = logfile.previous()
         except StopIteration:
-            log = None
+            pass
         return log
 
     def last_edit(self, request):
@@ -301,38 +415,34 @@
         @rtype: bool
         @return: true, if this page is writable or does not exist
         """
-        return os.access(self._text_filename(), os.W_OK) or not self.exists()
+        info = self.getInfo()
+        if info.get('textfile'):
+            # If we have a text file, check if its writeable
+            return os.access(info['textfile'], os.W_OK)
+        # We have not text file, so we can write new one
+        return True
 
-    def isUnderlayPage(self, includeDeleted=True):
+    def isUnderlayPage(self):
         """ Does this page live in the underlay dir?
 
-        Return true even if the data dir has a copy of this page. To
-        check for underlay only page, use ifUnderlayPage() and not
-        isStandardPage()
-
-        @param includeDeleted: include deleted pages
         @rtype: bool
         @return: true if page lives in the underlay dir
         """
-        return self.exists(domain='underlay', includeDeleted=includeDeleted)
-
-    def isStandardPage(self, includeDeleted=True):
+        return self.getInfo().get('domain') == 'underlay'
+            
+    def isStandardPage(self):
         """ Does this page live in the data dir?
 
-        Return true even if this is a copy of an underlay page. To check
-        for data only page, use isStandardPage() and not isUnderlayPage().
-
-        @param includeDeleted: include deleted pages
         @rtype: bool
         @return: true if page lives in the data dir
         """
-        return self.exists(domain='standard', includeDeleted=includeDeleted)
-                
-    def exists(self, rev=0, domain=None, includeDeleted=False):
+        return self.getInfo().get('domain') == 'standard'
+                            
+    def exists(self, domain=None, includeDeleted=False):
         """ Does this page exist?
 
         This is the lower level method for checking page existence. Use
-        the higher level methods isUnderlayPagea and isStandardPage for
+        the convinience methods isUnderlayPage and isStandardPage for
         cleaner code.
         
         @param rev: revision to look for. Default check current
@@ -346,26 +456,18 @@
         if domain == 'underlay' and not self.request.cfg.data_underlay_dir:
             return False
 
+        # Get info for page, either using the default domain search
+        # method, or for specific domain.
+        if domain == None:
+            info = self.getInfo()
+        else:
+            info = self.getInfoForDomain(domain)
+        
         if includeDeleted:
-            # Look for page directory, ignore page state
-            if domain is None:
-                domains = ['underlay', 'standard']
-            else:
-                domains = [domain]
-            for domain in domains:
-                pagedir = self.getPagePath(force_pagedir=domain, check_create=0)
-                if os.path.exists(pagedir):
-                    return True
-            return False
+            return info.get('path') is not None
         else:
-            # Look for non-deleted pages only, using get_rev
-            if not rev and self.rev:
-                rev = self.rev
-            if domain is not None:
-                domain = self.getPagePath(force_pagedir=domain, check_create=0)
-            d, d, exists = self.get_rev(domain, rev)
-            return exists
-
+            return info.get('textfile') is not None
+                
     def size(self, rev=0):
         """ Get Page size.
         
@@ -376,12 +478,15 @@
             if self._raw_body is not None:
                 return len(self._raw_body)
 
-        try:
-            return os.path.getsize(self._text_filename(rev=rev))
-        except EnvironmentError, e:
-            import errno
-            if e.errno == errno.ENOENT: return 0
-            raise
+        textfile = self.getInfo().get('textfile')
+        if textfile is not None:
+            try:
+                return os.path.getsize(self._text_filename(rev=rev))
+            except EnvironmentError, e:
+                import errno
+                if e.errno == errno.ENOENT: return 0
+                raise
+        return 0
              
     def mtime_usecs(self):
         """
@@ -451,7 +556,7 @@
             rootpage = self.request.rootpage
             
         # Get pages in pages directory
-        dir = rootpage.getPagePath('pages')
+        dir = rootpage.getPagesPath()
         pages = self.listPages(dir, user)
         
         # Merge with underlay pages
@@ -492,26 +597,10 @@
         @return: raw page contents of this page
         """
         if self._raw_body is None:
-            # try to open file
-            try:
-                file = codecs.open(self._text_filename(), 'rb', config.charset)
-            except IOError, er:
-                import errno
-                if er.errno == errno.ENOENT:
-                    # just doesn't exist, return empty text (note that we
-                    # never store empty pages, so this is detectable and also
-                    # safe when passed to a function expecting a string)
-                    return ""
-                else:
-                    raise er
-
-            # read file content and make sure it is closed properly
-            try:
-                text = file.read()
-                text = self.decodeTextMimeType(text)
+            textfile = self.getInfo().get('textfile')
+            if textfile:
+                text = self.readTextFile(textfile)
                 self.set_raw_body(text)
-            finally:
-                file.close()
 
         return self._raw_body
 
@@ -527,7 +616,7 @@
         """
         self._raw_body = body
         self._raw_body_modified = modified
-
+            
     def url(self, request, querystr=None, escape=1):
         """ Return complete URL for this page, including scriptname
 
@@ -539,8 +628,7 @@
         @rtype: str
         @return: complete url of this page, including scriptname
         """
-        url = '%s/%s' % (request.getScriptname(),
-                     wikiutil.quoteWikinameURL(self.page_name))
+        url = '%s/%s' % (request.getScriptname(), self.urlName())
         
         if querystr:
             querystr = web.makeQueryString(querystr)
@@ -577,7 +665,7 @@
             text = self.split_title(request)
 
         # Create url, excluding scriptname
-        url = wikiutil.quoteWikinameURL(self.page_name)
+        url = self.urlName()
         if querystr:
             querystr = web.makeQueryString(querystr)
             # makeQueryString does not escape any more
@@ -690,9 +778,10 @@
         # count hit?
         if keywords.get('count_hit', 0):
             eventlog.EventLog(request).add(request, 'VIEWPAGE', {'pagename': self.page_name})
-
+        request.clock.start('load_page_body')
         # load the text
         body = self.get_raw_body()
+        request.clock.stop('load_page_body')
 
         # if necessary, load the default formatter
         if self.default_formatter:
@@ -701,7 +790,8 @@
         self.formatter.setPage(self)
         if self.hilite_re: self.formatter.set_highlight_re(self.hilite_re)
         request.formatter = self.formatter
-        
+
+        request.clock.start('process_pi')
         # default is wiki markup
         pi_format = self.cfg.default_markup or "wiki"
         pi_formatargs = ''
@@ -826,6 +916,8 @@
 
         # Save values for later use
         self.pi_format = pi_format
+        request.clock.stop('process_pi')
+
 
         # start document output
         doc_leader = self.formatter.startDocument(self.page_name)
@@ -854,7 +946,7 @@
                     
                 link = '%s/%s?action=fullsearch&amp;value=%s&amp;literal=1&amp;case=1&amp;context=180' % (
                     request.getScriptname(),
-                    wikiutil.quoteWikinameURL(self.page_name),
+                    self.urlName(),
                     urllib.quote_plus(page_needle.encode(config.charset), ''))
                 title = self.split_title(request)
                 if self.rev:
@@ -922,7 +1014,10 @@
             request.write("<strong>%s</strong><br>" % _("You are not allowed to view this page."))
         else:
             # parse the text and send the page content
-            self.send_page_content(request, Parser, body, format_args=pi_formatargs, do_cache=do_cache)
+            request.clock.start('send_page_content')
+            self.send_page_content(request, Parser, body,
+                                   format_args=pi_formatargs, do_cache=do_cache)
+            request.clock.stop('send_page_content')
 
             # check for pending footnotes
             if getattr(request, 'footnotes', None):
@@ -1001,7 +1096,6 @@
         @param body: text of the wiki page
         @param needsupdate: if 1, force update of the cached compiled page
         """
-        request.clock.start('send_page_content')
         formatter_name = self.getFormatterName()
 
         # if we should not or can not use caching
@@ -1018,8 +1112,7 @@
         cache = caching.CacheEntry(request, arena, key)
         code = None
 
-        if cache.needsUpdate(self._text_filename(),
-                             self.getPagePath('attachments', check_create=0)):
+        if cache.needsUpdate(self._text_filename(), self.getAttachmentPath()):
             needsupdate = 1
 
         # load cache
@@ -1094,14 +1187,12 @@
         self.cache_mtime = cache.mtime()
 
         # TODO: move this into theme (currently used only by classic)
-        qpage = wikiutil.quoteWikinameURL(self.page_name)
+        qpage = self.urlName()
         url = "%s?action=refresh&amp;arena=Page.py&amp;key=%s" % (qpage, key)        
         link = wikiutil.link_tag(request, url, _("RefreshCache", formatted=False))
         date = self.request.user.getFormattedDateTime(cache.mtime())
         fragment = link + ' ' +  _('(cached %s)') % date
         self.request.add2footer('RefreshCache', fragment)
-
-        request.clock.stop('send_page_content')
         
     def _emptyPageText(self, request):
         """
@@ -1125,17 +1216,20 @@
         @return: page revisions
         """
         revisions = []
-        if self.page_name:
-            rev_dir = self.getPagePath('revisions', check_create=0)
-            if os.path.isdir(rev_dir):
-                for rev in os.listdir(rev_dir):
+        info = self.getInfo()
+        path = info.get('path')
+        if path:
+            path = os.path.join(path, 'revisions')
+            if os.path.isdir(path):
+                for name in os.listdir(path):
                     try:
-                        revint = int(rev)
-                        revisions.append(revint)
+                        number = int(name)
+                        revisions.append(number)
                     except ValueError:
                         pass
                 revisions.sort()
                 revisions.reverse()
+                
         return revisions
 
 
@@ -1261,9 +1355,9 @@
             import wikiacl
             return wikiacl.AccessControlList(request)
         # mtime check for forked long running processes
-        fn = self._text_filename()
+        fn = self.getInfo().get('textfile')
         acl = None
-        if os.path.exists(fn):
+        if fn is not None:
             mtime = os.path.getmtime(fn)
         else:
             mtime = 0
@@ -1325,19 +1419,17 @@
             if name.startswith('.') or name.startswith('#') or name == 'CVS':
                 continue
             
-            # Filter deleted pages
-            pagedir = os.path.join(dir, name)
-            d, d, exists = self.get_rev(pagedir)
-            if not exists:
-                continue
-            
             # Unquote - from this point name is Unicode
             name = wikiutil.unquoteWikiname(name)
-            
+
             # Filter meta-pages like editor backups
             if name.endswith(u'/MoinEditorBackup'):
                 continue           
-            
+
+            # Filter deleted pages
+            if not Page(self.request, name).exists():
+                continue   
+                        
             # Filter out page user may not read
             if user and not user.may.read(name):
                 continue  


--- orig/MoinMoin/PageEditor.py
+++ mod/MoinMoin/PageEditor.py
@@ -8,7 +8,7 @@
 
 import os, time, codecs
 from MoinMoin import caching, config, user, util, wikiutil, error
-from MoinMoin.Page import Page
+from MoinMoin.Page import Page, _Page
 from MoinMoin.widget import html
 from MoinMoin.widget.dialog import Status
 from MoinMoin.logfile import editlog, eventlog
@@ -78,7 +78,7 @@
 #############################################################################
 ### PageEditor - Edit pages
 #############################################################################
-class PageEditor(Page):
+class PageEditor(_Page):
     """Editor for a wiki page."""
 
     # exceptions for .saveText()
@@ -112,7 +112,7 @@
         self._ = request.getText
         self.cfg = request.cfg
 
-        Page.__init__(self, request, page_name, **keywords)
+        _Page.__init__(self, request, page_name, **keywords)
 
         self.do_revision_backup = keywords.get('do_revision_backup', 1)
         self.do_editor_backup = keywords.get('do_editor_backup', 1)
@@ -311,7 +311,7 @@
         # send form
         self.request.write('<form id="editor" method="post" action="%s/%s#preview">' % (
             self.request.getScriptname(),
-            wikiutil.quoteWikinameURL(self.page_name),
+            self.urlName() ,
             ))
 
         # yet another weird workaround for broken IE6 (it expands the text
@@ -709,9 +709,11 @@
         """
         Copy a page from underlay directory to page directory
         """
-        src = self.getPagePath(force_pagedir='underlay', check_create=0)
-        dst = self.getPagePath(force_pagedir='standard', check_create=0)
-        if src and dst and src != dst and os.path.exists(src):
+        info = self.getInfo()
+        if info.get('domain') == 'underlay':
+            src = info('path')
+            dst = os.path.join(self.request.cfg.data_dir, 'pages',
+                               self.getStorageName())
             try:
                 os.rmdir(dst) # simply remove empty dst dirs
                 # XXX in fact, we should better remove anything we regard as an
@@ -721,20 +723,23 @@
                 pass
             if not os.path.exists(dst):
                 filesys.copytree(src, dst)
+                # Invalidate page info, it will be re created when needed.
+                self._pageinfo = None
 
     def _get_pragmas(self, text):
         pragmas = {}
-        for line in text.split('\n'):
-            if not line or line[0] != '#':
-                # end of pragmas
-                break
-            
-            if len(line) > 1 and line[1] == '#':
-                # a comment within pragmas
-                continue
-            
-            verb, args = (line[1:]+' ').split(' ', 1)
-            pragmas[verb.lower()] = args.strip()
+        if text is not None:       
+            for line in text.split('\n'):
+                if not line or line[0] != '#':
+                    # end of pragmas
+                    break
+
+                if len(line) > 1 and line[1] == '#':
+                    # a comment within pragmas
+                    continue
+
+                verb, args = (line[1:]+' ').split(' ', 1)
+                pragmas[verb.lower()] = args.strip()
             
         return pragmas
 
@@ -749,8 +754,7 @@
         was_deprecated = self._get_pragmas(self.get_raw_body()).has_key("deprecated")
 
         self.copypage()
-
-        pagedir = self.getPagePath(check_create=0)
+        pagedir = os.path.join(self.cfg.data_dir, 'pages', self.getStorageName())
         revdir = os.path.join(pagedir, 'revisions')
         cfn = os.path.join(pagedir,'current')
         clfn = os.path.join(pagedir,'current-locked')
@@ -1032,7 +1036,7 @@
 
     def _filename(self):
         """get path and filename for edit-lock file"""
-        return self.pageobj.getPagePath('edit-lock', isfile=1)
+        return self.pageobj.getEditLockPath()
 
 
     def _readLockFile(self):
@@ -1043,30 +1047,35 @@
         self.timestamp = 0
 
         if self.locktype:
-            try:
-                entry = editlog.EditLog(self.request, filename=self._filename()).next()
-            except StopIteration:
-                entry = None
-                                                    
-            if entry:
-                self.owner = entry.userid or entry.addr
-                self.owner_html = entry.getEditor(self.request)
-                self.timestamp = wikiutil.version2timestamp(entry.ed_time_usecs)
-
+            filename = self._filename()
+            if filename is not None:
+                try:
+                    entry = editlog.EditLog(self.request, filename=self._filename()).next()
+                except StopIteration:
+                    entry = None
+
+                if entry:
+                    self.owner = entry.userid or entry.addr
+                    self.owner_html = entry.getEditor(self.request)
+                    self.timestamp = wikiutil.version2timestamp(entry.ed_time_usecs)
 
     def _writeLockFile(self):
         """Write new lock file."""
         self._deleteLockFile()
-        try:
-            editlog.EditLog(self.request, filename=self._filename()).add(
-               self.request, wikiutil.timestamp2version(self.now), 0, "LOCK", self.page_name)
-        except IOError:
-            pass
+        filename = self._filename()
+        if filename is not None:
+            try:
+                editlog.EditLog(self.request, filename=self._filename()).add(
+                    self.request, wikiutil.timestamp2version(self.now), 0, "LOCK", self.page_name)
+            except IOError:
+                pass
 
     def _deleteLockFile(self):
         """Delete the lock file unconditionally."""
-        try:
-            os.remove(self._filename())
-        except OSError:
-            pass
+        filename = self._filename()
+        if filename is not None:
+            try:
+                os.remove(filename)
+            except OSError:
+                pass
 


--- orig/MoinMoin/action/AttachFile.py
+++ mod/MoinMoin/action/AttachFile.py
@@ -58,7 +58,7 @@
             filesys.makeDirs(attach_dir)
     else:
         # send file via CGI, from page storage area
-        attach_dir = Page(request, pagename).getPagePath("attachments", check_create=create)
+        attach_dir = Page(request, pagename).getAttachmentPath(repair=create)
 
     return attach_dir
 
@@ -97,7 +97,8 @@
     """
     _ = request.getText
     attach_dir = getAttachDir(request, pagename)
-    if not os.path.exists(attach_dir): return ''
+    if attach_dir is None:
+        return ''
 
     files = os.listdir(attach_dir)
     if not files: return ''
@@ -122,7 +123,7 @@
 
     attach_dir = getAttachDir(request, pagename)
     files = []
-    if os.path.isdir(attach_dir):
+    if attach_dir and os.path.isdir(attach_dir):
         files = os.listdir(attach_dir)
     page = Page(request, pagename)
     # TODO: remove escape=0 in 1.4
@@ -247,7 +248,7 @@
 
 def _get_files(request, pagename):
     attach_dir = getAttachDir(request, pagename)
-    if os.path.isdir(attach_dir):
+    if attach_dir and os.path.isdir(attach_dir):
         files = map(lambda a: a.decode(config.charset), os.listdir(attach_dir))
         files.sort()
         return files


--- orig/MoinMoin/caching.py
+++ mod/MoinMoin/caching.py
@@ -28,7 +28,7 @@
                 os.chmod(self.arena_dir, 0777 & config.umask)
         else: # arena is in fact a page object
             cache_dir = None
-            self.arena_dir = arena.getPagePath('cache', check_create=1)
+            self.arena_dir = arena.getCachePath()
         self.key = key
 
     def _filename(self):


--- orig/MoinMoin/logfile/editlog.py
+++ mod/MoinMoin/logfile/editlog.py
@@ -75,9 +75,9 @@
         if filename == None:
             rootpagename = kw.get('rootpagename', None)
             if rootpagename:
-                filename = Page(request, rootpagename).getPagePath('edit-log', isfile=1)
+                filename = Page(request, rootpagename).getEditLogPath()
             else:
-                filename = request.rootpage.getPagePath('edit-log', isfile=1)
+                filename = request.rootpage.getEditLogPath()
         LogFile.__init__(self, filename, buffer_size)
         self._NUM_FIELDS = 9
         self._usercache = {}


--- orig/MoinMoin/logfile/eventlog.py
+++ mod/MoinMoin/logfile/eventlog.py
@@ -15,9 +15,9 @@
             rootpagename = kw.get('rootpagename', None)
             if rootpagename:
                 from MoinMoin.Page import Page
-                filename = Page(request, rootpagename).getPagePath('event-log', isfile=1)
+                filename = Page(request, rootpagename).getEventLogPath()
             else:
-                filename = request.rootpage.getPagePath('event-log', isfile=1)
+                filename = request.rootpage.getEventLogPath()
         LogFile.__init__(self, filename, buffer_size)
 
     def add(self, request, eventtype, values=None, add_http_info=1, mtime_usecs=None):


--- orig/MoinMoin/request.py
+++ mod/MoinMoin/request.py
@@ -67,6 +67,7 @@
         self._known_actions = None
         self.sent_headers = 0
         self.user_headers = []
+        self._pages = {}
 
         # check for some asses trying to use us as a proxy:
         self.forbidden = False
@@ -969,7 +970,7 @@
         # Set Pragma for http 1.0 caches
         # See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-14.32
         self.setHttpHeader('Pragma: no-cache')
-       
+        
     def setCookie(self):
         """ Set cookie for the current user
         
@@ -1056,6 +1057,7 @@
             del self.user
             del self.theme
             del self.dicts
+            del self._pages
         except:
             pass
 


--- orig/MoinMoin/server/standalone.py
+++ mod/MoinMoin/server/standalone.py
@@ -148,12 +148,14 @@
         if config.memoryProfile:
             config.memoryProfile.addRequest()
         try:
-            req = RequestStandAlone(self)
+            
             if config.hotshotProfile and moin_requests_done > 0:
                 # Don't profile the first request, its not interesting
                 # for long running process, and its very expensive.
+                req = config.hotshotProfile.runcall(RequestStandAlone, self)
                 config.hotshotProfile.runcall(req.run)
             else:
+                req = RequestStandAlone(self)
                 req.run()
         except socket.error, err:
             # Ignore certain errors


--- orig/MoinMoin/theme/__init__.py
+++ mod/MoinMoin/theme/__init__.py
@@ -287,6 +287,7 @@
         @return: navibar html
         """
         request = self.request
+        request.clock.start('navibar')
         found = {} # pages we found. prevent duplicates
         items = [] # navibar items
         item = u'<li class="%s">%s</li>'
@@ -329,6 +330,7 @@
 %s
 </ul>
 ''' % items
+        request.clock.stop('navibar')
         return html
  
     def get_icon(self, icon):
@@ -929,6 +931,7 @@
 
         # Make new edit bar
         request = self.request
+        request.clock.start('theme_editbar')
         _ = self.request.getText
         link = wikiutil.link_tag
         quotedname = wikiutil.quoteWikinameURL(page.page_name)
@@ -963,6 +966,7 @@
         
         # cache for next call
         self._cache[cacheKey] = html
+        request.clock.stop('theme_editbar')
         return html
 
     def startPage(self):


--- orig/MoinMoin/wikiutil.py
+++ mod/MoinMoin/wikiutil.py
@@ -926,6 +926,7 @@
     @keyword body_attr: additional <body> attributes
     @keyword body_onload: additional "onload" JavaScript code
     """
+    request.clock.start('send_title')
     from MoinMoin.Page import Page
     _ = request.getText
     pagename = keywords.get('pagename', '')
@@ -1121,13 +1122,15 @@
         request.themedict = d
 
         # now call the theming code to do the rendering
+        request.clock.start('theme_header')
         output.append(theme.header(d))
+        request.clock.stop('theme_header')
     
     # emit it
     request.write(''.join(output))
     output = []
     request.flush()
-
+    request.clock.stop('send_title')
 
 def send_footer(request, pagename, **keywords):
     """
@@ -1139,6 +1142,7 @@
     @keyword showpage: true, when link back to page is wanted (default: false)
     @keyword print_mode: true, when page is displayed in Print mode
     """
+    request.clock.start('send_footer')
     d = request.themedict
     theme = request.theme
 
@@ -1150,6 +1154,7 @@
         # This is used only by classic now, kill soon
         d['footer_fragments'] = request._footer_fragments
         request.write(theme.footer(d, **keywords))
+    request.clock.stop('send_footer')
 
     
 ########################################################################



