Attachment 'page-state.patch'

Download

   1 * looking for arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-538 to compare with
   2 * comparing to arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-538
   3 M  MoinMoin/theme/__init__.py
   4 M  MoinMoin/macro/EditedSystemPages.py
   5 M  MoinMoin/action/RenamePage.py
   6 M  MoinMoin/Page.py
   7 M  MoinMoin/PageEditor.py
   8 M  MoinMoin/request.py
   9 M  MoinMoin/util/filesys.py
  10 
  11 * modified files
  12 
  13 --- orig/MoinMoin/Page.py
  14 +++ mod/MoinMoin/Page.py
  15 @@ -21,16 +21,16 @@
  16  _acl_cache = {}
  17  
  18  class Page:
  19 -    """Page - Manage an (immutable) page associated with a WikiName.
  20 -       To change a page's content, use the PageEditor class.
  21 +    """ Page - manage immutable page associated with a WikiName.
  22 +    
  23 +    To change a page's content, use the PageEditor class.
  24      """
  25  
  26      # Header regular expression, used to get header boundaries
  27      header_re = r'(^#+.*(?:\n\s*)+)+'
  28  
  29      def __init__(self, request, page_name, **keywords):
  30 -        """
  31 -        Create page object.
  32 +        """ Init page object.
  33  
  34          Note that this is a 'lean' operation, since the text for the page
  35          is loaded on demand. Thus, things like `Page(name).link_to()` are
  36 @@ -38,6 +38,7 @@
  37  
  38          @param page_name: WikiName of the page
  39          @keyword rev: number of older revision
  40 +        @keyword is_rootpage: does this page is the wiki rootpage
  41          @keyword formatter: formatter instance
  42          """
  43          self.rev = keywords.get('rev', 0) # revision of this page
  44 @@ -62,134 +63,143 @@
  45          self._raw_body_modified = 0
  46          self.hilite_re = None
  47          self.language = None
  48 -
  49 -        self.reset()
  50 -
  51 -    def reset(self):
  52 -        """ Reset page state """
  53 -        page_name = self.page_name
  54 -        # page_name quoted for file system usage, needs to be reset to
  55 -        # None when pagename changes
  56 -        
  57 -        qpagename = wikiutil.quoteWikinameFS(page_name)
  58 -        self.page_name_fs = qpagename
  59 -
  60 -        # the normal and the underlay path used for this page
  61 -        if not self.cfg.data_underlay_dir is None:
  62 -            underlaypath = os.path.join(self.cfg.data_underlay_dir, "pages", qpagename)
  63 -        else:
  64 -            underlaypath = None
  65 -        if self.is_rootpage: # we have no request.rootpage yet!
  66 -            if not page_name:
  67 -                normalpath = self.cfg.data_dir
  68 -            else:
  69 -                raise NotImplementedError(
  70 -                    "TODO: handle other values of rootpage (not used yet)")
  71 -        else:
  72 -            normalpath = self.request.rootpage.getPagePath("pages", qpagename,
  73 -                                                           check_create=0, use_underlay=0)
  74          
  75 -        # TUNING - remember some essential values
  76 +        # Add page info dict to cache, create new empty if its not there
  77 +        if not self.page_name in self.request.pages:
  78 +            self.reset()
  79 +        else:
  80 +            self._info = self.request.pages[self.page_name]     
  81  
  82 -        # does the page come from normal page storage (0) or from
  83 -        # underlay dir (1) (can be used as index into following lists)
  84 -        self._underlay = None
  85 +    def reset(self):
  86 +        """ Called when page information has been changed
  87  
  88 -        # path to normal / underlay page dir
  89 -        self._pagepath = [normalpath, underlaypath]
  90 +        After this call, any page state query, such as exists() will
  91 +        cause another disk access and rebuild of the page cache state.
  92 +        """
  93 +        self._info = self.request.pages[self.page_name] = {}
  94  
  95 -        # path to normal / underlay page file
  96 -        self._pagefile = [normalpath, underlaypath]
  97 +    def storageName(self):
  98 +        """ Return the storage name of the page """
  99 +        if not 'storage_name' in self._info:
 100 +            self._info['storage_name'] = wikiutil.quoteWikinameFS(self.page_name)
 101 +        return self._info['storage_name']
 102  
 103 -        # *latest* revision of this page XXX needs to be reset to None
 104 -        # when current rev changes
 105 -        self._current_rev = [None, None]
 106 +    def _getInfoFromPage(self, path):
 107 +        """ Get page current information
 108  
 109 -        # does a page in _pagepath (current revision) exist?  XXX needs
 110 -        # to be reset when rev is created/deleted
 111 -        self._exists = [None, None]
 112 +        Access the disk directly, does not change page state.
 113          
 114 -    def get_current_from_pagedir(self, pagedir):
 115 -        """ get the current revision number from an arbitrary pagedir.
 116 -            does not modify page object's state, uncached, direct disk access.
 117 -            @param pagedir: the pagedir with the 'current' file to read
 118 -            @return: int currentrev
 119 -        """
 120 -        revfilename = os.path.join(pagedir, 'current')
 121 -        try:
 122 -            revfile = open(revfilename)
 123 -            revstr = revfile.read().strip()
 124 -            revfile.close()
 125 -            rev = int(revstr)
 126 -        except:
 127 -            rev = 99999999 # XXX
 128 -        return rev
 129 -        
 130 -    def get_rev_dir(self, pagedir, rev=0):
 131 -        """ 
 132 -        get a revision of a page from an arbitrary pagedir.
 133 -        does not modify page object's state, uncached, direct disk access.
 134 -        @param pagedir: the path to the pagedir
 135 -        @param rev: int revision to get (default is 0 and means the current
 136 +        @param path: the path to the pagedir
 137 +        @param revision: int revision to get (default is None and means the current
 138                      revision (in this case, the real revint is returned)
 139 -        @return: (str pagefilename, int realrevint, bool exists)
 140 +        @rtype: dict
 141 +        @return: dictionary with page information
 142          """
 143 -        if rev == 0:
 144 -            rev = self.get_current_from_pagedir(pagedir)
 145 -        
 146 -        revstr = '%08d' % rev
 147 -        pagefile = os.path.join(pagedir, 'revisions', revstr)
 148 -        exists = os.path.exists(pagefile)
 149 -        return pagefile, rev, exists
 150 -    
 151 -    def get_rev(self, use_underlay=-1, rev=0):
 152 -        """ 
 153 -        get a revision of this page 
 154 -        @param use_underlay: -1 == auto, 0 == normal, 1 == underlay
 155 -        @param rev: int revision to get (default is 0 and means the current
 156 -                    revision (in this case, the real revint is returned)
 157 -        @return: (str pagefilename, int realrevint, bool exists)
 158 +        # Get current revision
 159 +        current = os.path.join(path, 'current')
 160 +        try:
 161 +            f = file(current)
 162 +            filename = f.read().strip()
 163 +            f.close()
 164 +            revision = int(filename)
 165 +        except (IOError, OSError, ValueError):
 166 +            # No current file of broken file
 167 +            # Keeping old code behavior
 168 +            filename = '99999999'
 169 +            revision =  99999999
 170 +
 171 +        # Make information dict
 172 +        textfile = os.path.join(path, 'revisions', filename)
 173 +        info = {
 174 +            'revision': revision,
 175 +            'textfile': textfile,
 176 +            'exists': os.path.exists(textfile),
 177 +            }
 178 +
 179 +        return info
 180 +
 181 +    def _getInfoFromDomain(self, domain, name):
 182 +        """ Get info from one of the wiki domains,
 183 +
 184 +        Access the disk directly, does not change page state.
 185 +
 186 +        @param domain: the wiki domain, 'standard' or 'underlay'
 187 +        @param name: the storage name of the page
 188 +        @rtype: dict
 189 +        @return: dict with page information
 190 +        """        
 191 +        # Setup path
 192 +        options = {
 193 +            'standard': self.cfg.data_dir,
 194 +            'underlay': self.cfg.data_underlay_dir,
 195 +            }
 196 +        path = options[domain]
 197 +        info = {}
 198 +        if path:
 199 +            pagepath = os.path.join(path, 'pages', name)
 200 +            if os.path.exists(pagepath):
 201 +                info['path'] = pagepath
 202 +                info['standard'] = domain == 'standard'
 203 +                info.update(self._getInfoFromPage(pagepath))
 204 +                                    
 205 +        return info
 206 +
 207 +    def getInfo(self):
 208 +        """ Get page current information
 209 +
 210 +        This is the central point to get information about the current
 211 +        state of the page. All accessors calls use this to get page
 212 +        information.
 213 +
 214 +        This method use lower level _get methods to get information from
 215 +        the disk, then return cached values. self._info is actually
 216 +        bound to request.pages[pagename] dict. All instances of same
 217 +        page share same information.
 218 +
 219 +        @rtype: dict
 220 +        @return: page information dict
 221          """
 222 -        if use_underlay == -1:
 223 -            if self._underlay is not None and self._pagepath[self._underlay] is not None:
 224 -                underlay = self._underlay
 225 -                pagedir = self._pagepath[underlay]
 226 +        if not self._info.get('checked'):
 227 +            # This is the first time, get info from disk
 228 +            if self is self.request.rootpage:
 229 +                # The wiki rootpage
 230 +                self._info['path'] = self.cfg.data_dir
 231 +                self._info['exists'] = True
 232              else:
 233 -                underlay, pagedir = self.getPageStatus(check_create=0)
 234 -        else:
 235 -            underlay, pagedir = use_underlay, self._pagepath[use_underlay]
 236 +                name = self.storageName()
 237 +                info = self._getInfoFromDomain('standard', name)
 238 +                self._info.update(info)
 239 +                if not info.get('exists'):
 240 +                    # If the page does not exists in the standard domain,
 241 +                    # look for it in underlay.
 242 +                    info = self._getInfoFromDomain('underlay', name)
 243 +                    self._info.update(info)
 244 +                    
 245 +            # Mark as checked - no more disk access
 246 +            self._info['checked'] = True
 247  
 248 -        if rev == 0:
 249 -            if self._current_rev[underlay] is None:
 250 -                realrev = self.get_current_from_pagedir(pagedir)
 251 -                self._current_rev[underlay] = realrev # XXX XXX
 252 -            else:
 253 -                realrev = self._current_rev[underlay]
 254 -        
 255 -            _exists = self._exists[underlay]
 256 -            _realrev = self._current_rev[underlay]
 257 -            _pagefile = self._pagefile[underlay]
 258 -            if _pagefile is not None and \
 259 -               _realrev is not None and _exists is not None:
 260 -                return _pagefile, _realrev, _exists
 261 -        else:
 262 -            realrev = rev
 263 -        
 264 -        pagefile, realrev, exists = self.get_rev_dir(pagedir, realrev)
 265 -        if rev == 0:
 266 -            self._exists[underlay] = exists # XXX XXX
 267 -            self._current_rev[underlay] = realrev # XXX XXX
 268 -            self._pagefile[underlay] = pagefile # XXX XXX
 269 -            
 270 -        return pagefile, realrev, exists
 271 +        return self._info
 272  
 273      def current_rev(self):
 274 -        pagefile, rev, exists = self.get_rev()
 275 -        return rev
 276 +        """ Return the current revision of the page
 277  
 278 +        @rtype: int
 279 +        @return: current revision
 280 +        """
 281 +        # If page exists, we simply return the real revision from the
 282 +        # disk.
 283 +        if self.exists():
 284 +            return self.getInfo()['revision']
 285 +
 286 +        # For non existing pages, the old behavior is to return
 287 +        # 99999999, which is quite broken, and need to be fixed in the
 288 +        # future.
 289 +        return 99999999            
 290 +        
 291      def get_real_rev(self):
 292 -        """Returns the real revision number of this page. A rev=0 is
 293 -        translated to the current revision.
 294 +        """ Returns the real revision number of this page.
 295 +
 296 +        A rev=0 is translated to the current revision.
 297  
 298          @returns: revision number > 0
 299          @rtype: int
 300 @@ -197,106 +207,88 @@
 301          if self.rev == 0:
 302              return self.current_rev()
 303          return self.rev
 304 -
 305 -    def getPageBasePath(self, use_underlay):
 306 -        """
 307 +    
 308 +    def getPagePath(self, *args, **kw):
 309 +        """ Get page path to storage area
 310 +        
 311          Get full path to a page-specific storage area. `args` can
 312 -        contain additional path components that are added to the base path.
 313 +        contain additional path components that are added to the base
 314 +        path.
 315 +
 316 +        This method return real paths for existing pages. For non
 317 +        existing pages, the default path will be at the data_dir, or as
 318 +        specified by use_underlay.
 319  
 320 -        @param use_underlay: force using a specific pagedir, default '-1'
 321 -                                '-1' = automatically choose page dir
 322 -                                '1' = use underlay page dir
 323 -                                '0' = use standard page dir
 324 +        @param args: additional path components
 325 +        @keyword use_underlay: use the underlay page directory (default -1)
 326 +            -1 will look for the page and return the true page path, or a
 327 +            page path in the standard directory.
 328 +        @keyword check_create: create the path (including all missing
 329 +            directories) if it does not exist. (default True)
 330 +        @keyword isfile: is the last component in args a filename? (default False)
 331          @rtype: string
 332          @return: the full path to the storage area
 333          """
 334 -        standardpath, underlaypath = self._pagepath
 335 -        if underlaypath is None:
 336 -            use_underlay = 0
 337 -        
 338 -        # self is a NORMAL page
 339 -        if not self is self.request.rootpage:
 340 -            if use_underlay == -1: # automatic
 341 -                if self._underlay is None:
 342 -                    underlay, path = 0, standardpath
 343 -                    pagefile, rev, exists = self.get_rev(use_underlay=0)
 344 -                    if not exists:
 345 -                        pagefile, rev, exists = self.get_rev(use_underlay=1)
 346 -                        if exists:                         
 347 -                            underlay, path = 1, underlaypath
 348 -                    self._underlay = underlay # XXX XXX
 349 -                else:
 350 -                    underlay = self._underlay
 351 -                    path = self._pagepath[underlay]
 352 -            else: # normal or underlay
 353 -                underlay, path = use_underlay, self._pagepath[use_underlay]                
 354 +        use_underlay = kw.get('use_underlay', -1)
 355          
 356 -        # self is rootpage
 357 +        if self is self.request.rootpage and self.page_name == '':
 358 +            # Root page
 359 +            if use_underlay == 1:
 360 +                path = self.cfg.data_underlay_dir
 361 +            else:
 362 +                # use_underlay -1 is ignored, does not make sense here.
 363 +                path = self.cfg.data_dir
 364          else:
 365 -            # our current rootpage is not a toplevel, but under another page
 366 -            if self.page_name:
 367 -                # this assumes flat storage of pages and sub pages on same level
 368 -                if use_underlay == -1: # automatic
 369 -                    if self._underlay is None:
 370 -                        underlay, path = 0, standardpath
 371 -                        pagefile, rev, exists = self.get_rev(use_underlay=0)
 372 -                        if not exists:
 373 -                            pagefile, rev, exists = self.get_rev(use_underlay=1)
 374 -                            if exists:
 375 -                                underlay, path = 1, underlaypath
 376 -                        self._underlay = underlay # XXX XXX
 377 -                    else:
 378 -                        underlay = self._underlay
 379 -                        path = self._pagepath[underlay]
 380 -                else: # normal or underlay
 381 -                    underlay, path = use_underlay, self._pagepath[use_underlay]                
 382 -            
 383 -            # our current rootpage is THE virtual rootpage, really at top of all
 384 +            # Regular page
 385 +
 386 +            # Default page paths
 387 +            # TODO: cache these instead of recreating for each call?
 388 +            name = self.storageName()
 389 +            standard = os.path.join(self.cfg.data_dir, 'pages', name)
 390 +            if self.cfg.data_underlay_dir is not None:
 391 +                underlay = os.path.join(self.cfg.data_underlay_dir, 'pages',
 392 +                                        name)
 393              else:
 394 -                # 'auto' doesn't make sense here. maybe not even 'underlay':
 395 -                if use_underlay == 1:
 396 -                    underlay, path = 1, self.cfg.data_underlay_dir
 397 -                # no need to check 'standard' case, we just use path in that case!
 398 +                underlay = None
 399 +
 400 +            if use_underlay == -1:
 401 +                if self.exists():
 402 +                    # For existing pages, return the path to the page
 403 +                    path = self.getInfo()['path']
 404                  else:
 405 -                    # this is the location of the virtual root page
 406 -                    underlay, path = 0, self.cfg.data_dir
 407 -        
 408 -        return underlay, path
 409 +                    # If the page does not exists, return the path to
 410 +                    # where it will be created - in the standard directory.
 411 +                    path = standard
 412 +            elif use_underlay == 1:
 413 +                # Return path in the underlay directory
 414 +                path = underlay
 415 +            elif use_underlay == 0:
 416 +                # Return path in the standard directory
 417 +                path = standard
 418 +            else:
 419 +                raise ValueError('Invalid value for use_underlay: %r' %
 420 +                                 use_underlay)
 421  
 422 -    def getPageStatus(self, *args, **kw):
 423 -        """
 424 -        Get full path to a page-specific storage area. `args` can
 425 -        contain additional path components that are added to the base path.
 426 +        # Create full path
 427 +        if path:
 428 +            fullpath = os.path.join(*((path,) + args))
 429 +            
 430 +            # Self repair storage directories
 431 +            # TODO: move this into a separate function?
 432 +            check_create = kw.get('check_create', True)
 433 +            if check_create:
 434 +                isfile = kw.get('isfile', False)
 435 +                if isfile:
 436 +                    dirname, filename = os.path.split(fullpath)
 437 +                else:
 438 +                    dirname = fullpath
 439 +                if not os.path.exists(dirname):
 440 +                    filesys.makeDirs(dirname)
 441  
 442 -        @param args: additional path components
 443 -        @keyword use_underlay: force using a specific pagedir, default '-1'
 444 -                                -1 = automatically choose page dir
 445 -                                1 = use underlay page dir
 446 -                                0 = use standard page dir
 447 -        @keyword check_create: if true, ensures that the path requested really exists
 448 -                               (if it doesn't, create all directories automatically).
 449 -                               (default true)
 450 -        @keyword isfile: is the last component in args a filename? (default is false)
 451 -        @rtype: string
 452 -        @return: the full path to the storage area
 453 -        """
 454 -        check_create = kw.get('check_create', 1)
 455 -        isfile = kw.get('isfile', 0)
 456 -        use_underlay = kw.get('use_underlay', -1)
 457 -        underlay, path = self.getPageBasePath(use_underlay)
 458 -        fullpath = os.path.join(*((path,) + args))
 459 -        if check_create:
 460 -            if isfile:
 461 -                dirname, filename = os.path.split(fullpath)
 462 -            else:
 463 -                dirname = fullpath
 464 -            if not os.path.exists(dirname):
 465 -                filesys.makeDirs(dirname)
 466 -        return underlay, fullpath
 467 -    
 468 -    def getPagePath(self, *args, **kw):
 469 -        return self.getPageStatus(*args, **kw)[1]
 470 +            return fullpath
 471  
 472 +        return path
 473 +        
 474      def split_title(self, request, force=0):
 475          """
 476          Return a string with the page name split by spaces, if
 477 @@ -319,30 +311,45 @@
 478          return splitted
 479  
 480      def _text_filename(self, **kw):
 481 -        """
 482 -        The name of the page file, possibly of an older page.
 483 +        """ The name of the page file, possibly of an older page.
 484          
 485          @keyword rev: page revision, overriding self.rev
 486          @rtype: string
 487          @return: complete filename (including path) to this page
 488          """
 489 +        # Force filename, ignore page state
 490          if hasattr(self, '_text_filename_force'):
 491              return self._text_filename_force
 492 +
 493 +        # rev keyword override the current revision
 494          rev = kw.get('rev', 0)
 495          if not rev and self.rev:
 496              rev = self.rev
 497 -        fname, rev, exists = self.get_rev(-1, rev)
 498 -        return fname
 499 +        
 500 +        if rev == 0 and self.exists():
 501 +            # Return the current real textfile
 502 +            textfile = self.getInfo()['textfile']
 503 +        else:               
 504 +            # For all other caese, return the a default textfile, which
 505 +            # might not exists.
 506 +            textfile = self.getPagePath('revisions', '%08d' % rev, check_create=0)
 507 +
 508 +        return textfile
 509  
 510      def _tmp_filename(self):
 511 -        """
 512 -        The name of the temporary file used while saving.
 513 +        """ The name of the temporary file used while saving.
 514 +
 515 +        TODO: can we use tempfile module instead?
 516          
 517          @rtype: string
 518          @return: temporary filename (complete path + filename)
 519          """
 520 -        rnd = random.randint(0,1000000000)
 521 -        tmpname = os.path.join(self.cfg.data_dir, '#%s.%d#' % (self.page_name_fs, rnd))
 522 +        import time
 523 +        
 524 +        path = self.getPagePath('temp')
 525 +        now = int(time.time())
 526 +        rnd = random.randint(0,100000)
 527 +        tmpname = os.path.join(path, '%d.%d' % (now, rnd))
 528          return tmpname
 529  
 530      # XXX TODO clean up the mess, rewrite _last_edited, last_edit, lastEditInfo for new logs,
 531 @@ -426,88 +433,81 @@
 532          """
 533          return os.access(self._text_filename(), os.W_OK) or not self.exists()
 534  
 535 -    def isUnderlayPage(self, includeDeleted=True):
 536 +    def isUnderlay(self):
 537          """ Does this page live in the underlay dir?
 538  
 539 -        Return true even if the data dir has a copy of this page. To
 540 -        check for underlay only page, use ifUnderlayPage() and not
 541 -        isStandardPage()
 542 -
 543 -        @param includeDeleted: include deleted pages
 544          @rtype: bool
 545          @return: true if page lives in the underlay dir
 546          """
 547 -        return self.exists(domain='underlay', includeDeleted=includeDeleted)
 548 +        return not self.isStandard()
 549  
 550 -    def isStandardPage(self, includeDeleted=True):
 551 +    def isStandard(self):
 552          """ Does this page live in the data dir?
 553  
 554 -        Return true even if this is a copy of an underlay page. To check
 555 -        for data only page, use isStandardPage() and not isUnderlayPage().
 556 +        Return true even if this is a copy of an underlay page.
 557 +
 558 +        @rtype: bool
 559 +        @return: true if page lives in the data dir
 560 +        """
 561 +        info = self.getInfo()
 562 +        return info.get('exists') and info.get('standard')
 563 +
 564 +    def isOverlay(self):
 565 +        """ Is this a standard page overlaying an underlay page?
 566 +
 567 +        Access the disk and updage page state.
 568  
 569 -        @param includeDeleted: include deleted pages
 570          @rtype: bool
 571          @return: true if page lives in the data dir
 572          """
 573 -        return self.exists(domain='standard', includeDeleted=includeDeleted)
 574 +        info = self.getInfo()
 575 +        if not 'overlay' in info:
 576 +            overlay = False
 577 +            if info.get('exists') and info.get('standard'):
 578 +                underlay = self._getInfoFromDomain('underlay',
 579 +                                                  self.storageName())
 580 +                overlay = underlay.get('exists', False)
 581 +            info['overlay'] = overlay
 582 +            
 583 +        return info['overlay']
 584 +
 585 +    def isDeleted(self):
 586 +        """ Is this page deleted?
 587 +
 588 +        Deleted page have a directory but no text file
 589 +        """
 590 +        info = self.getInfo()
 591 +        return info.get('path') and not info['exists']
 592                  
 593 -    def exists(self, rev=0, domain=None, includeDeleted=False):
 594 +    def exists(self):
 595          """ Does this page exist?
 596 -
 597 -        This is the lower level method for checking page existence. Use
 598 -        the higher level methods isUnderlayPagea and isStandardPage for
 599 -        cleaner code.
 600          
 601          @param rev: revision to look for. Default check current
 602 -        @param domain: where to look for the page. Default look in all,
 603 -            available values: 'underlay', 'standard'
 604 -        @param includeDeleted: ignore page state, just check its pagedir
 605          @rtype: bool
 606          @return: true, if page exists
 607          """
 608 -        # Edge cases
 609 -        if domain == 'underlay' and not self.request.cfg.data_underlay_dir:
 610 -            return False
 611 -                        
 612 -        if includeDeleted:                
 613 -            # Look for page directory, ignore page state
 614 -            if domain is None:
 615 -                checklist = [0, 1]
 616 -            else:
 617 -                checklist = [domain == 'underlay']
 618 -            for use_underlay in checklist:
 619 -                pagedir = self.getPagePath(use_underlay=use_underlay, check_create=0)
 620 -                if os.path.exists(pagedir):
 621 -                    return True
 622 -            return False
 623 -        else:
 624 -            # Look for non-deleted pages only, using get_rev
 625 -            if not rev and self.rev:
 626 -                rev = self.rev
 627 -
 628 -            if domain is None:
 629 -                use_underlay = -1
 630 -            else:
 631 -                use_underlay = domain == 'underlay'
 632 -            d, d, exists = self.get_rev(use_underlay, rev)
 633 -            return exists
 634 -
 635 +        info = self.getInfo()
 636 +        return info.get('exists', False)
 637 +        
 638      def size(self, rev=0):
 639          """ Get Page size.
 640          
 641          @rtype: int
 642          @return: page size, 0 for non-existent pages.
 643          """
 644 -        if rev == self.rev: # same revision as self
 645 +        if rev == self.rev:
 646 +            # Report size of current revision
 647              if self._raw_body is not None:
 648                  return len(self._raw_body)
 649  
 650 +        # Report size for specific revision, that might not exists
 651          try:
 652              return os.path.getsize(self._text_filename(rev=rev))
 653          except EnvironmentError, e:
 654              import errno
 655 -            if e.errno == errno.ENOENT: return 0
 656 -            raise
 657 +            if e.errno == errno.ENOENT:
 658 +                return 0
 659 +            raise e
 660               
 661      def mtime_usecs(self):
 662          """
 663 @@ -568,8 +568,9 @@
 664              # WARNING: SLOW
 665              pages = self.getPageList(user='')
 666          else:
 667 -            pages = self.request.getPages()
 668 -            if not pages:
 669 +            if self.request.pages_complete:
 670 +                pages = self.request.pages
 671 +            else:
 672                  pages = self._listPages()
 673          count = len(pages)
 674          self.request.clock.stop('getPageCount')
 675 @@ -591,8 +592,13 @@
 676          if user is None:
 677              user = self.request.user
 678  
 679 -        acl = self.getACL(self.request)
 680 -        return acl.may(self.request, user.name, what)
 681 +        # User may read is cached per user name in page info
 682 +        key = u'user.%s.%s' % (user.name, what)
 683 +        if not key in self._info:
 684 +            acl = self.getACL(self.request)
 685 +            self._info[key] = acl.may(self.request, user.name, what)
 686 +
 687 +        return self._info[key]
 688  
 689      def getPageList(self, user=None, exists=1, filter=None):
 690          ''' List user readable pages under current page
 691 @@ -628,10 +634,15 @@
 692          # Check input
 693          if user is None:
 694              user = request.user
 695 -
 696 -        # Get pages cache or create it
 697 -        cache = request.getPages()
 698 -        if not cache:
 699 +        # ACL right cached by user name and right name for quick second
 700 +        # access.
 701 +        if user:
 702 +            userkey = u'user.%s.read' % (user.name)
 703 +
 704 +        # Get pages cache, that may contain already partial pages data
 705 +        cache = request.pages
 706 +        if not request.pages_complete:
 707 +            # Update cache from disk
 708              for name in self._listPages():
 709                  # Unquote file system names
 710                  pagename = wikiutil.unquoteWikiname(name)
 711 @@ -639,15 +650,21 @@
 712                  # Filter those annoying editor backups
 713                  if pagename.endswith(u'/MoinEditorBackup'):
 714                      continue
 715 -                
 716 -                cache[pagename] = 1
 717 +
 718 +                # Add new pages
 719 +                if not pagename in cache:
 720 +                    cache[pagename] = {}
 721 +                                
 722 +            # Set complete flag to True, so next call will use cache
 723 +            request.pages_complete = True
 724  
 725          if user or exists or filter:
 726              # Filter names
 727              pages = []
 728              for name in cache:
 729                  page = None
 730 -
 731 +                info = cache[name]
 732 +                
 733                  # First, custom filter - exists and acl check are very
 734                  # expensive!
 735                  if filter and not filter(name):
 736 @@ -655,18 +672,24 @@
 737  
 738                  # Filter deleted pages
 739                  if exists:
 740 -                    page = Page(request, name)
 741 -                    if not page.exists():
 742 +                    if not 'checked' in info:
 743 +                        page = Page(request, name)
 744 +                        if not page.exists():
 745 +                            continue
 746 +                    elif not info.get('exists'):
 747                          continue
 748 -
 749 +                    
 750                  # Filter out page user may not read. Bypass user.may.read
 751                  # to avoid duplicate page instance and page exists call. 
 752                  if user:
 753 -                    if not page:
 754 -                        page = Page(request, name)
 755 -                    if not page.userMay('read', user=user):
 756 +                    if not userkey in info:
 757 +                        if not page:
 758 +                            page = Page(request, name)
 759 +                        if not page.userMay('read', user=user):
 760 +                            continue
 761 +                    elif not info[userkey]:
 762                          continue
 763 -
 764 +                    
 765                  pages.append(name)
 766          else:
 767              pages = cache.keys()
 768 @@ -711,7 +734,7 @@
 769              path = self.getPagePath('pages', use_underlay=1)
 770              underlay = self._listPageInPath(path)
 771              pages.update(underlay)
 772 -                                
 773 +                                                    
 774          return pages
 775  
 776      def _listPageInPath(self, path):
 777 @@ -735,7 +758,7 @@
 778              if name.startswith('.') or name.startswith('#') or name == 'CVS':
 779                  continue
 780              
 781 -            pages[name] = 1
 782 +            pages[name] = None
 783              
 784          return pages
 785  
 786 @@ -773,7 +796,7 @@
 787                  file.close()
 788  
 789          return self._raw_body
 790 -
 791 +    
 792      def set_raw_body(self, body, modified=0):
 793          """ Set the raw body text (prevents loading from disk).
 794  
 795 @@ -1430,7 +1453,8 @@
 796          # Lazy compile regex on first use. All instances share the
 797          # same regex, compiled once when the first call in an instance is done.
 798          if isinstance(self.__class__.header_re, (str, unicode)):
 799 -            self.__class__.header_re = re.compile(self.__class__.header_re, re.MULTILINE | re.UNICODE)
 800 +            self.__class__.header_re = re.compile(self.__class__.header_re,
 801 +                                                  re.MULTILINE | re.UNICODE)
 802  				    
 803          body = self.get_raw_body() or ''
 804          header = self.header_re.search(body)
 805 @@ -1453,7 +1477,8 @@
 806          # Lazy compile regex on first use. All instances share the
 807          # same regex, compiled once when the first call in an instance is done.
 808          if isinstance(self.__class__.header_re, (str, unicode)):
 809 -            self.__class__.header_re = re.compile(self.__class__.header_re, re.MULTILINE | re.UNICODE)
 810 +            self.__class__.header_re = re.compile(self.__class__.header_re,
 811 +                                                  re.MULTILINE | re.UNICODE)
 812  
 813          body = self.get_raw_body() or ''
 814          header = self.header_re.search(body)
 815 @@ -1545,7 +1570,9 @@
 816              return wikiacl.AccessControlList(request)
 817  
 818          # Get page state
 819 -        pagefile, revision, exists = self.get_rev()
 820 +        info = self.getInfo()
 821 +        revision = info.get('revision')
 822 +        exists = info.get('exists')
 823          if not exists:
 824              # Get previous revision
 825              revisions = self.getRevList()
 826 @@ -1553,7 +1580,7 @@
 827                  revision = revisions[1]
 828              
 829          # Try to get value from cache
 830 -        key = (request.cfg.siteid, self.page_name)
 831 +        key = (self.cfg.siteid, self.page_name)
 832          aclRevision, acl = _acl_cache.get(key, (None, None))
 833          
 834          if aclRevision != revision or acl is None:
 835 
 836 
 837 --- orig/MoinMoin/PageEditor.py
 838 +++ mod/MoinMoin/PageEditor.py
 839 @@ -759,14 +759,23 @@
 840  
 841          self.copypage()
 842  
 843 -        pagedir = self.getPagePath(check_create=0)
 844 +        # Write always on the standard directory, never change the
 845 +        # underlay directory copy!
 846 +        pagedir = self.getPagePath(use_underlay=0, check_create=0)
 847 +
 848          revdir = os.path.join(pagedir, 'revisions')
 849          cfn = os.path.join(pagedir,'current')
 850          clfn = os.path.join(pagedir,'current-locked')
 851 -
 852 +        
 853          # !!! these log objects MUST be created outside the locked area !!!
 854 +
 855 +        # The local log should be the standard edit log, not the
 856 +        # underlay copy log!
 857 +        pagelog = self.getPagePath('edit-log', use_underlay=0, isfile=1)
 858 +        llog = editlog.EditLog(self.request, filename=pagelog,
 859 +                               uid_override=self.uid_override)
 860 +        # Open the global log
 861          glog = editlog.EditLog(self.request, uid_override=self.uid_override)
 862 -        llog = editlog.EditLog(self.request, rootpagename=self.page_name, uid_override=self.uid_override)
 863          
 864          if not os.path.exists(pagedir): # new page, create and init pagedir
 865              os.mkdir(pagedir)
 866 
 867 
 868 --- orig/MoinMoin/action/RenamePage.py
 869 +++ mod/MoinMoin/action/RenamePage.py
 870 @@ -97,17 +97,17 @@
 871  
 872          # Valid new name
 873          newpage = PageEditor(self.request, newpagename)
 874 -
 875 -        # Check whether a page with the new name already exists
 876 -        if newpage.exists(includeDeleted=1):
 877 -            return self.pageExistsError(newpagename)
 878 -        
 879 +    
 880          # Get old page text
 881          savetext = self.page.get_raw_body()
 882  
 883          oldpath = self.page.getPagePath(check_create=0)
 884          newpath = newpage.getPagePath(check_create=0)
 885  
 886 +        # Check whether a page with the new name already exists
 887 +        if os.path.exists(newpath):
 888 +            return self.pageExistsError(newpagename)
 889 +
 890          # Rename page
 891  
 892          # NOTE: might fail if another process created newpagename just
 893 @@ -125,7 +125,7 @@
 894          except OSError, err:
 895              # Try to understand what happened. Maybe its better to check
 896              # the error code, but I just reused the available code above...
 897 -            if newpage.exists(includeDeleted=1):
 898 +            if os.path.exists(newpath):
 899                  return self.pageExistsError(newpagename)
 900              else:
 901                  self.error = _('Could not rename page because of file system'
 902 
 903 
 904 
 905 --- orig/MoinMoin/macro/EditedSystemPages.py
 906 +++ mod/MoinMoin/macro/EditedSystemPages.py
 907 @@ -21,12 +21,11 @@
 908          from MoinMoin.Page import Page
 909  
 910          # Get page list for current user (use this as admin), filter
 911 -        # pages that are both underlay and standard pages.
 912 +        # standard pages that overlay underlay pages.
 913          def filter(name):
 914              page = Page(self.request, name)
 915 -            return (page.isStandardPage(includeDeleted=0) and
 916 -                    page.isUnderlayPage(includeDeleted=0))
 917 -
 918 +            return page.isOverlay()
 919 +                
 920          # Get page filtered page list. We don't need to filter by
 921          # exists, because our filter check this already.
 922          pages = self.request.rootpage.getPageList(filter=filter, exists=0)
 923 
 924 
 925 --- orig/MoinMoin/request.py
 926 +++ mod/MoinMoin/request.py
 927 @@ -67,7 +67,9 @@
 928          self._known_actions = None
 929  
 930          # Pages meta data that we collect in one request
 931 -        self._pages = {}
 932 +        self.pages = {}
 933 +        # Does pages contains all pages in the wiki?
 934 +        self.pages_complete = False
 935                      
 936          self.sent_headers = 0
 937          self.user_headers = []
 938 @@ -355,7 +357,7 @@
 939              actions.extend(extension_actions)           
 940             
 941              # TODO: Use set when we require Python 2.3
 942 -            actions = dict(zip(actions, [''] * len(actions)))            
 943 +            actions = dict(zip(actions, [None] * len(actions)))            
 944              self.known_actions = actions
 945  
 946          # Return a copy, so clients will not change the dict.
 947 @@ -372,10 +374,9 @@
 948          @return: dict of avaiable actions
 949          """
 950          if self._available_actions is None:
 951 -            # Add actions for existing pages only!, incliding deleted pages.
 952 +            # Add actions for existing pages only!, including deleted pages.
 953              # Fix *OnNonExistingPage bugs.
 954 -            if not (page.exists(includeDeleted=1) and
 955 -                    self.user.may.read(page.page_name)):
 956 +            if not (page.exists() or page.isDeleted()) and page.userMay('read'):
 957                  return []
 958  
 959              # Filter non ui actions (starts with lower case letter)
 960 @@ -391,11 +392,10 @@
 961  
 962              # Filter actions by page type, acl and user state
 963              excluded = []
 964 -            if ((page.isUnderlayPage() and not page.isStandardPage()) or
 965 -                not self.user.may.write(page.page_name)):
 966 -                # Prevent modification of underlay only pages, or pages
 967 -                # the user can't write to
 968 -                excluded = [u'RenamePage', u'DeletePage',] # AttachFile must NOT be here!
 969 +            if page.isUnderlay() or not page.userMay('write'):
 970 +                # Protect underlay pages or pages the user may not write
 971 +                # AttachFile must NOT be here!
 972 +                excluded = [u'RenamePage', u'DeletePage',]
 973              elif not self.user.valid:
 974                  # Prevent rename and delete for non registered users
 975                  excluded = [u'RenamePage', u'DeletePage']
 976 @@ -407,16 +407,6 @@
 977  
 978          # Return a copy, so clients will not change the dict.
 979          return self._available_actions.copy()
 980 -
 981 -    def getPages(self):
 982 -        """ Return a page meta data dict
 983 -
 984 -        _pages dict contain meta data about pages that we collect in one
 985 -        request. This save the need to access the disk every time we
 986 -        need information about the same page. This is a very common case
 987 -        according to the profiles.
 988 -        """
 989 -        return self._pages
 990      
 991      def redirect(self, file=None):
 992          if file: # redirect output to "file"
 993 
 994 
 995 --- orig/MoinMoin/theme/__init__.py
 996 +++ mod/MoinMoin/theme/__init__.py
 997 @@ -502,7 +502,7 @@
 998          @rtype: bool
 999          @return: true if should show page info
1000          """
1001 -        if page.exists() and self.request.user.may.read(page.page_name):
1002 +        if page.exists() and page.userMay('read'):
1003              # These  actions show the  page content.
1004              # TODO: on new action, page info will not show. A better
1005              # solution will be if the action itself answer the question:
1006 @@ -769,17 +769,21 @@
1007          @rtype: bool
1008          @return: true if editbar should show
1009          """
1010 -        # Show editbar only for existing pages, including deleted pages,
1011 -        # that the user may read. If you may not read, you can't edit,
1012 -        # so you don't need editbar.
1013 -        if (page.exists(includeDeleted=1) and
1014 -            self.request.user.may.read(page.page_name)):
1015 -            form = self.request.form
1016 -            action = form.get('action', [''])[0]
1017 -            # Do not show editbar on edit or edit preview
1018 -            return not (action == 'edit' or form.has_key('button_preview'))
1019 -        
1020 -        return False
1021 +        key = 'shouldShowEditbar'
1022 +        should = False
1023 +        if not key in self._cache:
1024 +            # Show editbar only for existing pages, including deleted pages,
1025 +            # that the user may read. If you may not read, you can't edit,
1026 +            # so you don't need editbar.
1027 +            if (page.exists() or page.isDeleted()) and page.userMay('read'):
1028 +                form = self.request.form
1029 +                action = form.get('action', [''])[0]
1030 +                # Do not show editbar on edit or edit preview
1031 +                should = not (action == 'edit' or
1032 +                              form.has_key('button_preview'))
1033 +            self._cache[key] = should
1034 +
1035 +        return self._cache[key]
1036  
1037      def subscribeLink(self, page):
1038          """ Return subscribe/unsubscribe link to valid users
1039 @@ -952,10 +956,12 @@
1040             add(parent.link_to(request, _("Show Parent", formatted=False))) 
1041          
1042          # Page actions
1043 -        if page.isWritable() and request.user.may.write(page.page_name):
1044 +        if page.isWritable() and page.userMay('write'):
1045              add(link(request, quotedname + '?action=edit', _('Edit')))
1046          else:
1047              add(_('Immutable Page', formatted=False))              
1048 +
1049 +        # Refresh cache is disabled because it was misused by user as 'reload'
1050          if 0: # page.canUseCache():
1051              query = '%(quotedname)s?action=refresh&arena=Page.py&key=%(key)s'
1052              query = query % {
1053 @@ -963,6 +969,8 @@
1054                  'key': page.getFormatterName(),
1055                  }
1056              add(link(request, query, _('Refresh', formatted=False)))
1057 +
1058 +        # Other stuff
1059          add(link(request, quotedname + '?action=diff', _('Show Changes', formatted=False)))
1060          add(link(request, quotedname + '?action=info', _('Get Info', formatted=False)))
1061          add(self.subscribeLink(page))
1062 
1063 
1064 --- orig/MoinMoin/util/filesys.py
1065 +++ mod/MoinMoin/util/filesys.py
1066 @@ -105,7 +105,7 @@
1067          except (IOError, os.error), why:
1068              errors.append((srcname, dstname, why))
1069      if errors:
1070 -        raise Error, errors
1071 +        raise RuntimeError, errors
1072  
1073  # Code could come from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203
1074  

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2007-07-04 15:29:57, 7.7 KB) [[attachment:Slideshow.py]]
  • [get | view] (2004-10-20 05:28:16, 72.8 KB) [[attachment:alum.jpg]]
  • [get | view] (2005-04-14 19:56:30, 68.2 KB) [[attachment:farm.tbz]]
  • [get | view] (2005-05-28 17:05:11, 31.2 KB) [[attachment:me-with-naomi.jpg]]
  • [get | view] (2005-04-14 19:55:22, 8.6 KB) [[attachment:multiconfig.patch]]
  • [get | view] (2005-01-16 05:36:39, 41.4 KB) [[attachment:page-state.patch]]
  • [get | view] (2004-12-28 02:41:46, 36.7 KB) [[attachment:plugin.patch]]
  • [get | view] (2004-10-21 18:49:52, 22.1 KB) [[attachment:text_html.py]]
  • [get | view] (2005-04-18 07:20:14, 1.4 KB) [[attachment:wiki_icons.png]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.