Attachment 'page-refactor.patch'

Download

   1 * looking for arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-460 to compare with
   2 * comparing to arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-460
   3 M  MoinMoin/logfile/eventlog.py
   4 M  MoinMoin/logfile/editlog.py
   5 M  MoinMoin/server/standalone.py
   6 M  MoinMoin/theme/__init__.py
   7 M  MoinMoin/Page.py
   8 M  MoinMoin/PageEditor.py
   9 M  MoinMoin/action/AttachFile.py
  10 M  MoinMoin/caching.py
  11 M  MoinMoin/request.py
  12 M  MoinMoin/wikiutil.py
  13 
  14 * modified files
  15 
  16 --- orig/MoinMoin/Page.py
  17 +++ mod/MoinMoin/Page.py
  18 @@ -7,7 +7,7 @@
  19  """
  20  
  21  import StringIO, os, re, urllib, os.path, random, codecs
  22 -from MoinMoin import config, caching, user, util, wikiutil
  23 +from MoinMoin import config, caching, user, util, wikiutil, error
  24  from MoinMoin.logfile import eventlog
  25  from MoinMoin.util import filesys, web
  26  
  27 @@ -20,11 +20,20 @@
  28  header_re = re.compile(r'(^#+.*(?:\n\s*)+)+', re.UNICODE | re.MULTILINE)
  29  
  30  
  31 -class Page:
  32 +# Page factory - return pages from request._pages dict
  33 +def Page(request, page_name, **kw):
  34 +    # Note - ignore kw on page creation, its broken design
  35 +    ## return _Page(request, page_name)
  36 +    if not page_name in request._pages:
  37 +        page = _Page(request, page_name)
  38 +        request._pages[page_name] = page
  39 +    return request._pages[page_name]
  40 +    
  41 +class _Page:
  42      """Page - Manage an (immutable) page associated with a WikiName.
  43         To change a page's content, use the PageEditor class.
  44      """
  45 -
  46 +    
  47      def __init__(self, request, page_name, **keywords):
  48          """
  49          Create page object.
  50 @@ -45,13 +54,29 @@
  51          self._raw_body_modified = 0
  52          self.hilite_re = None
  53          self.language = None
  54 -        
  55 +
  56          if keywords.has_key('formatter'):
  57              self.formatter = keywords.get('formatter')
  58              self.default_formatter = 0
  59          else:
  60              self.default_formatter = 1
  61  
  62 +        # Page info is filled with data as data is requested
  63 +        self._urlName = None
  64 +        self._storageName = None
  65 +        self._pageinfo = None
  66 +
  67 +        ## request.log('Created page %s' % repr(page_name))
  68 +
  69 +    def storageName(self):
  70 +        if self._storageName is None:
  71 +            self._storageName = wikiutil.quoteWikinameFS(self.page_name)
  72 +        return self._storageName
  73 +
  74 +    def urlName(self):
  75 +        if self._urlName is None:
  76 +            self._urlName = wikiutil.quoteWikinameURL(self.page_name)
  77 +        return self._urlName
  78  
  79      def split_title(self, request, force=0):
  80          """
  81 @@ -74,37 +99,9 @@
  82          splitted = SPLIT_RE.sub(r'\1 \2', self.page_name)
  83          return splitted
  84  
  85 -    def get_rev(self, pagedir=None, rev=0):
  86 -        """ 
  87 -        get a revision of the page in this path
  88 -        @param pagedir: the path to the pagedir
  89 -        @param rev: int revision to get (default is 0 and means the current
  90 -                    revision (in this case, the real revint is returned)
  91 -        @return: (str pagefilename, int realrevint, bool exists)
  92 -        """
  93 -        if pagedir is None:
  94 -            pagedir = self.getPagePath(check_create=0)
  95 -            
  96 -        if rev == 0:
  97 -            revfilename = os.path.join(pagedir, 'current')
  98 -            try:
  99 -                revfile = open(revfilename)
 100 -                revstr = revfile.read().strip()
 101 -                revfile.close()
 102 -            except:
 103 -                revstr = '99999999' # XXX
 104 -            rev = int(revstr)
 105 -        else:
 106 -            revstr = '%08d' % rev
 107 -        
 108 -        pagefile = os.path.join(pagedir, 'revisions', revstr)
 109 -        exists = os.path.exists(pagefile)
 110 -        return pagefile, rev, exists
 111 -
 112      def current_rev(self):
 113 -        pagefile, rev, exists = self.get_rev()
 114 -        return rev
 115 -
 116 +        return self.getInfo().get('revision')
 117 +        
 118      def get_real_rev(self):
 119          """Returns the real revision number of this page. A rev=0 is
 120          translated to the current revision.
 121 @@ -116,85 +113,199 @@
 122              return self.current_rev()
 123          return self.rev
 124  
 125 -    def getPagePath(self, *args, **kw):
 126 -        """
 127 -        Get full path to a page-specific storage area. `args` can
 128 -        contain additional path components that are added to the base path.
 129 +    def hasRevision(self, revision):
 130 +        """ Does page has revision number? """
 131 +        info = self.getInfo()
 132 +        current = info['revision']
 133 +        if revision == current:
 134 +            return info.get('textfile') is not None
 135 +        elif revision < current:
 136 +            filename = '%08d' % revision
 137 +            path = os.path.join(info['path'], 'revisions', filename)
 138 +            return os.path.exists(path)
 139  
 140 -        @param args: additional path components
 141 -        @keyword force_pagedir: force using a specific pagedir, default 'auto'
 142 -                                'auto' = automatically choose page dir
 143 -                                'underlay' = use underlay page dir
 144 -                                'standard' = use standard page dir
 145 -        @keyword check_create: if true, ensures that the path requested really exists
 146 -                               (if it doesn't, create all directories automatically).
 147 -                               (default true)
 148 -        @keyword isfile: is the last component in args a filename? (default is false)
 149 -        @rtype: string
 150 -        @return: the full path to the storage area
 151 -        """
 152 -        force_pagedir = kw.get('force_pagedir', 'auto')
 153 -        check_create = kw.get('check_create', 1)
 154 -        qpagename = wikiutil.quoteWikinameFS(self.page_name)
 155 -        data_dir = self.cfg.data_dir
 156 -        # underlay is used to store system and help pages in a separate place
 157 -        underlay_dir = self.cfg.data_underlay_dir
 158 -        if underlay_dir:
 159 -            underlaypath = os.path.join(underlay_dir, "pages", qpagename)
 160 -        else:
 161 -            force_pagedir = 'standard'
 162 +        # No such revision
 163 +        return False
 164          
 165 -        # self is a NORMAL page
 166 -        if not self is self.request.rootpage:
 167 -            kw['check_create'] = 0 # we don't want to create empty page dirs:
 168 -            path = self.request.rootpage.getPagePath("pages", qpagename, **kw)
 169 -            if force_pagedir == 'auto': 
 170 -                pagefile, rev, exists = self.get_rev(path)
 171 -                if not exists:
 172 -                    pagefile, rev, exists = self.get_rev(underlaypath)
 173 -                    if exists:                         
 174 -                        path = underlaypath
 175 -            elif force_pagedir == 'underlay':
 176 -                path = underlaypath
 177 -            # no need to check 'standard' case, we just use path in that case!
 178 +    def getRevision(self, revision):
 179 +        """ Return page text as of revision, or None """
 180 +        if self.hasRevision(revision):
 181 +            info = self.getInfo()
 182 +            textfile = info.get('textfile')
 183 +            if textfile is None:
 184 +                textfile = '%08d' % revision
 185 +                textfile = os.path.join(info['path'], 'revisions', textfile)
 186 +            return self.readText(textfile)
 187 +        # No such revision or revision file missing
 188 +        return None
 189 +
 190 +    def readTextFile(self, path):
 191 +        """ Read text file at path """
 192 +        try:
 193 +            file = codecs.open(path, 'rb', config.charset)
 194 +        except IOError, err:
 195 +            import errno
 196 +            if er.errno == errno.ENOENT:
 197 +                # just doesn't exist, return empty text (note that we
 198 +                # never store empty pages, so this is detectable and also
 199 +                # safe when passed to a function expecting a string)
 200 +                return ""
 201 +            else:
 202 +                msg = '''
 203 +Could not read text file in %(path)s because of unexpected error: %(class)s: %(error)s
 204 +
 205 +Make sure page directories have correct ownership and permissions.
 206 +''' % {'path': path, 'class': err.__class__.__name__, 'error': str(err),}
 207 +                raise error.InternalError(msg)
 208 +
 209 +        # read file content and make sure it is closed properly
 210 +        try:
 211 +            text = file.read()
 212 +            text = self.decodeTextMimeType(text)
 213 +        finally:
 214 +            file.close()
 215 +
 216 +        return text
 217          
 218 -        # self is rootpage
 219 -        else:
 220 -            # our current rootpage is not a toplevel, but under another page
 221 -            if self.page_name:
 222 -                # this assumes flat storage of pages and sub pages on same level
 223 -                path = os.path.join(data_dir, "pages", qpagename)
 224 -                if force_pagedir == 'auto':
 225 -                    pagefile, rev, exists = self.get_rev(path)
 226 -                    if not exists:
 227 -                        pagefile, rev, exists = self.get_rev(underlaypath)
 228 -                        if exists:                         
 229 -                            path = underlaypath
 230 -                elif force_pagedir == 'underlay':
 231 -                    path = underlaypath
 232 -                # no need to check 'standard' case, we just use path in that case!
 233 -            
 234 -            # our current rootpage is THE virtual rootpage, really at top of all
 235 +    def getInfo(self):
 236 +        """ Return a dictionary with page information """
 237 +        if self._pageinfo is None:
 238 +            if self.page_name == u'':
 239 +                # The root page of the wiki, create virtual info
 240 +                info = {'path': self.cfg.data_dir,}
 241              else:
 242 -                # this is the location of the virtual root page
 243 -                path = data_dir
 244 -                # 'auto' doesn't make sense here. maybe not even 'underlay':
 245 -                if force_pagedir == 'underlay':
 246 -                    path = underlay_dir
 247 -                # no need to check 'standard' case, we just use path in that case!
 248 +                # Check where page live. First try at the data dir
 249 +                name = self.storageName()
 250 +                info = self.getInfoForDomain(domain='standard',
 251 +                                             name=name)
 252 +                if not info.get('textfile'):
 253 +                    # Try the underlay directory
 254 +                    info = self.getInfoForDomain(domain='underlay',
 255 +                                                 name=name)
 256 +            self._pageinfo = info
 257 +
 258 +        return self._pageinfo
 259                  
 260 -        fullpath = os.path.join(*((path,) + args))
 261 +    def getInfoForDomain(self, domain, name):
 262 +        """ Return information at specific domain """
 263 +        if domain == 'standard':
 264 +            dir = self.cfg.data_dir
 265 +        elif domain == 'underlay' and self.cfg.data_underlay_dir:
 266 +            dir = self.cfg.data_underlay_dir
 267 +        else:
 268 +            return {}
 269 +
 270 +        info = {}
 271 +        # Check if page directory exists
 272 +        path = os.path.join(dir, 'pages', name)
 273 +        if os.path.exists(path):
 274 +            info['domain'] = domain
 275 +            info['path'] = path
 276 +            # Try to get the current revision
 277 +            current = os.path.join(path, 'current')
 278 +            try:
 279 +                f = file(current)
 280 +                revision = f.read().strip()
 281 +                f.close()
 282 +                info['revision'] = int(revision)
 283 +                # Check if revision exists, or its a deleted page
 284 +                textfile = os.path.join(path, 'revisions', revision)
 285 +                if os.path.exists(textfile):
 286 +                    info['textfile'] = textfile
 287 +            except:
 288 +                # Page is deleted or broken
 289 +                pass
 290 +
 291 +        return info
 292 +    
 293 +    def _makeDirectory(self, path):
 294 +        """ Wraper around mkdir that raise fatal errors
 295 +
 296 +        This method should be used only by Page, as its immutable.
 297 +
 298 +        @param path: path to create
 299 +        """
 300 +        try:
 301 +            os.mkdir(path, 0777 & config.umask)
 302 +        except (IOError, OSError), err:
 303 +            # If the directory already exists, ignore the error
 304 +            if not os.path.isdir(path):
 305 +                msg = '''
 306 +Could not create page sub directory at %(path)s because of %(class)s:
 307 +%(error)s.
 308 +
 309 +Try to set permissions on the wiki directory again. The directory and
 310 +all its subdirectories and files should be readable, writable and
 311 +executable by the web server user and group.
 312 +''' % {'path': childpath, 'class': err.__class__.__name__, 'error': str(err),}
 313 +
 314 +                raise error.InternalError(msg)
 315 +                    
 316 +    def _getChildPath(self, child, repair=0):
 317 +        """ The central point to get page childern paths
 318 +
 319 +        This method should not be used out of Page. Use the convinience
 320 +        methods: getAttachmentPath, getCachePath etc.
 321          
 322 -        if check_create:
 323 -            if kw.get('isfile', 0):
 324 -                dirname, filename = os.path.split(fullpath)
 325 +        @param child: one of page children: pages, revisions,
 326 +            attachments etc (string).
 327 +        @param repair: if true, create missing directories
 328 +        @rtype: unicode
 329 +        @return: path to child or None if there is not page dir
 330 +
 331 +        """
 332 +        info = self.getInfo()
 333 +        # Return none for non existing page directory
 334 +        pagepath = info.get('path')
 335 +        if not pagepath:
 336 +            return None
 337 +
 338 +        # Get value for child key on first call
 339 +        if not info.has_key(child):
 340 +            # Check if child exists
 341 +            childpath = os.path.join(pagepath, child)
 342 +            if os.path.exists(childpath):
 343 +                info[child] = childpath
 344 +            elif repair:
 345 +                # Repair missing directories
 346 +                self._makeDirectory(childpath)
 347 +                info[child] = childpath
 348              else:
 349 -                dirname = fullpath
 350 -            if not os.path.exists(dirname):
 351 -                filesys.makeDirs(dirname)
 352 +                info[child] = None
 353 +            
 354 +        return info[child]
 355 +
 356 +    # Get methods ------------------------------------------------------
 357 +    
 358 +    # Public get methods that hides the internal structure of the
 359 +    # page. The caller does not have to know the names of the children.
 360 +
 361 +    # TODO: all these methods are wrong. Clients should not know the
 362 +    # page path at all. They should get objects, for example,
 363 +    # getEventLogPath should be called getEventLog and return the page
 364 +    # event log object. This will require larger refactoring.
 365 +    
 366 +    def getEditLockPath(self):
 367 +        return self._getChildPath('edit-lock')
 368 +
 369 +    def getEventLogPath(self):
 370 +        return self._getChildPath('event-log')
 371 +
 372 +    def getEditLogPath(self):
 373 +        return self._getChildPath('edit-log')
 374 +
 375 +    def getAttachmentPath(self, repair=0):
 376 +        return self._getChildPath('attachments', repair=repair)
 377 +    
 378 +    def getPagesPath(self, repair=0):
 379 +        return self._getChildPath('pages', repair=repair)
 380          
 381 -        return fullpath
 382 +    def getCachePath(self, repair=1):
 383 +        return self._getChildPath('cache', repair=repair)
 384  
 385 +    def getTempPath(self, repair=1):
 386 +        return self._getChildPath('temp', repair=repair)
 387 +        
 388      def _text_filename(self, **kw):
 389          """
 390          The name of the page file, possibly of an older page.
 391 @@ -205,12 +316,8 @@
 392          """
 393          if hasattr(self, '_text_filename_force'):
 394              return self._text_filename_force
 395 -        rev = kw.get('rev', 0)
 396 -        if not rev and self.rev:
 397 -            rev = self.rev
 398 -        fname, rev, exists = self.get_rev(None, rev)
 399 -        return fname
 400 -
 401 +        return self.getInfo().get('textfile')
 402 +    
 403      def _tmp_filename(self):
 404          """
 405          The name of the temporary file used while saving.
 406 @@ -218,20 +325,27 @@
 407          @rtype: string
 408          @return: temporary filename (complete path + filename)
 409          """
 410 -        rnd = random.randint(0,1000000000)
 411 -        tmpname = os.path.join(self.cfg.data_dir, ('#%s.%d#' % (wikiutil.quoteWikinameFS(self.page_name), rnd)))
 412 -        return tmpname
 413 +        tempdir = self.getTempPath()
 414 +        if not tempdir:
 415 +            return None
 416 +        
 417 +        name = str(long(time.time() * (10**6)))
 418 +        name = os.path.join(tempdir, name)
 419 +        return name
 420  
 421      # XXX TODO clean up the mess, rewrite _last_edited, last_edit, lastEditInfo for new logs,
 422      # XXX TODO do not use mtime() calls any more
 423      def _last_edited(self, request):
 424          from MoinMoin.logfile import editlog
 425 +        log = None
 426          try:
 427 -            logfile = editlog.EditLog(request, self.getPagePath('edit-log', check_create=0, isfile=1))
 428 -            logfile.to_end()
 429 -            log = logfile.previous()
 430 +            logpath = self.getEditLogPath()
 431 +            if logpath is not None:
 432 +                logfile = editlog.EditLog(request, logpath)
 433 +                logfile.to_end()
 434 +                log = logfile.previous()
 435          except StopIteration:
 436 -            log = None
 437 +            pass
 438          return log
 439  
 440      def last_edit(self, request):
 441 @@ -301,38 +415,34 @@
 442          @rtype: bool
 443          @return: true, if this page is writable or does not exist
 444          """
 445 -        return os.access(self._text_filename(), os.W_OK) or not self.exists()
 446 +        info = self.getInfo()
 447 +        if info.get('textfile'):
 448 +            # If we have a text file, check if its writeable
 449 +            return os.access(info['textfile'], os.W_OK)
 450 +        # We have not text file, so we can write new one
 451 +        return True
 452  
 453 -    def isUnderlayPage(self, includeDeleted=True):
 454 +    def isUnderlayPage(self):
 455          """ Does this page live in the underlay dir?
 456  
 457 -        Return true even if the data dir has a copy of this page. To
 458 -        check for underlay only page, use ifUnderlayPage() and not
 459 -        isStandardPage()
 460 -
 461 -        @param includeDeleted: include deleted pages
 462          @rtype: bool
 463          @return: true if page lives in the underlay dir
 464          """
 465 -        return self.exists(domain='underlay', includeDeleted=includeDeleted)
 466 -
 467 -    def isStandardPage(self, includeDeleted=True):
 468 +        return self.getInfo().get('domain') == 'underlay'
 469 +            
 470 +    def isStandardPage(self):
 471          """ Does this page live in the data dir?
 472  
 473 -        Return true even if this is a copy of an underlay page. To check
 474 -        for data only page, use isStandardPage() and not isUnderlayPage().
 475 -
 476 -        @param includeDeleted: include deleted pages
 477          @rtype: bool
 478          @return: true if page lives in the data dir
 479          """
 480 -        return self.exists(domain='standard', includeDeleted=includeDeleted)
 481 -                
 482 -    def exists(self, rev=0, domain=None, includeDeleted=False):
 483 +        return self.getInfo().get('domain') == 'standard'
 484 +                            
 485 +    def exists(self, domain=None, includeDeleted=False):
 486          """ Does this page exist?
 487  
 488          This is the lower level method for checking page existence. Use
 489 -        the higher level methods isUnderlayPagea and isStandardPage for
 490 +        the convinience methods isUnderlayPage and isStandardPage for
 491          cleaner code.
 492          
 493          @param rev: revision to look for. Default check current
 494 @@ -346,26 +456,18 @@
 495          if domain == 'underlay' and not self.request.cfg.data_underlay_dir:
 496              return False
 497  
 498 +        # Get info for page, either using the default domain search
 499 +        # method, or for specific domain.
 500 +        if domain == None:
 501 +            info = self.getInfo()
 502 +        else:
 503 +            info = self.getInfoForDomain(domain)
 504 +        
 505          if includeDeleted:
 506 -            # Look for page directory, ignore page state
 507 -            if domain is None:
 508 -                domains = ['underlay', 'standard']
 509 -            else:
 510 -                domains = [domain]
 511 -            for domain in domains:
 512 -                pagedir = self.getPagePath(force_pagedir=domain, check_create=0)
 513 -                if os.path.exists(pagedir):
 514 -                    return True
 515 -            return False
 516 +            return info.get('path') is not None
 517          else:
 518 -            # Look for non-deleted pages only, using get_rev
 519 -            if not rev and self.rev:
 520 -                rev = self.rev
 521 -            if domain is not None:
 522 -                domain = self.getPagePath(force_pagedir=domain, check_create=0)
 523 -            d, d, exists = self.get_rev(domain, rev)
 524 -            return exists
 525 -
 526 +            return info.get('textfile') is not None
 527 +                
 528      def size(self, rev=0):
 529          """ Get Page size.
 530          
 531 @@ -376,12 +478,15 @@
 532              if self._raw_body is not None:
 533                  return len(self._raw_body)
 534  
 535 -        try:
 536 -            return os.path.getsize(self._text_filename(rev=rev))
 537 -        except EnvironmentError, e:
 538 -            import errno
 539 -            if e.errno == errno.ENOENT: return 0
 540 -            raise
 541 +        textfile = self.getInfo().get('textfile')
 542 +        if textfile is not None:
 543 +            try:
 544 +                return os.path.getsize(self._text_filename(rev=rev))
 545 +            except EnvironmentError, e:
 546 +                import errno
 547 +                if e.errno == errno.ENOENT: return 0
 548 +                raise
 549 +        return 0
 550               
 551      def mtime_usecs(self):
 552          """
 553 @@ -451,7 +556,7 @@
 554              rootpage = self.request.rootpage
 555              
 556          # Get pages in pages directory
 557 -        dir = rootpage.getPagePath('pages')
 558 +        dir = rootpage.getPagesPath()
 559          pages = self.listPages(dir, user)
 560          
 561          # Merge with underlay pages
 562 @@ -492,26 +597,10 @@
 563          @return: raw page contents of this page
 564          """
 565          if self._raw_body is None:
 566 -            # try to open file
 567 -            try:
 568 -                file = codecs.open(self._text_filename(), 'rb', config.charset)
 569 -            except IOError, er:
 570 -                import errno
 571 -                if er.errno == errno.ENOENT:
 572 -                    # just doesn't exist, return empty text (note that we
 573 -                    # never store empty pages, so this is detectable and also
 574 -                    # safe when passed to a function expecting a string)
 575 -                    return ""
 576 -                else:
 577 -                    raise er
 578 -
 579 -            # read file content and make sure it is closed properly
 580 -            try:
 581 -                text = file.read()
 582 -                text = self.decodeTextMimeType(text)
 583 +            textfile = self.getInfo().get('textfile')
 584 +            if textfile:
 585 +                text = self.readTextFile(textfile)
 586                  self.set_raw_body(text)
 587 -            finally:
 588 -                file.close()
 589  
 590          return self._raw_body
 591  
 592 @@ -527,7 +616,7 @@
 593          """
 594          self._raw_body = body
 595          self._raw_body_modified = modified
 596 -
 597 +            
 598      def url(self, request, querystr=None, escape=1):
 599          """ Return complete URL for this page, including scriptname
 600  
 601 @@ -539,8 +628,7 @@
 602          @rtype: str
 603          @return: complete url of this page, including scriptname
 604          """
 605 -        url = '%s/%s' % (request.getScriptname(),
 606 -                     wikiutil.quoteWikinameURL(self.page_name))
 607 +        url = '%s/%s' % (request.getScriptname(), self.urlName())
 608          
 609          if querystr:
 610              querystr = web.makeQueryString(querystr)
 611 @@ -577,7 +665,7 @@
 612              text = self.split_title(request)
 613  
 614          # Create url, excluding scriptname
 615 -        url = wikiutil.quoteWikinameURL(self.page_name)
 616 +        url = self.urlName()
 617          if querystr:
 618              querystr = web.makeQueryString(querystr)
 619              # makeQueryString does not escape any more
 620 @@ -690,9 +778,10 @@
 621          # count hit?
 622          if keywords.get('count_hit', 0):
 623              eventlog.EventLog(request).add(request, 'VIEWPAGE', {'pagename': self.page_name})
 624 -
 625 +        request.clock.start('load_page_body')
 626          # load the text
 627          body = self.get_raw_body()
 628 +        request.clock.stop('load_page_body')
 629  
 630          # if necessary, load the default formatter
 631          if self.default_formatter:
 632 @@ -701,7 +790,8 @@
 633          self.formatter.setPage(self)
 634          if self.hilite_re: self.formatter.set_highlight_re(self.hilite_re)
 635          request.formatter = self.formatter
 636 -        
 637 +
 638 +        request.clock.start('process_pi')
 639          # default is wiki markup
 640          pi_format = self.cfg.default_markup or "wiki"
 641          pi_formatargs = ''
 642 @@ -826,6 +916,8 @@
 643  
 644          # Save values for later use
 645          self.pi_format = pi_format
 646 +        request.clock.stop('process_pi')
 647 +
 648  
 649          # start document output
 650          doc_leader = self.formatter.startDocument(self.page_name)
 651 @@ -854,7 +946,7 @@
 652                      
 653                  link = '%s/%s?action=fullsearch&amp;value=%s&amp;literal=1&amp;case=1&amp;context=180' % (
 654                      request.getScriptname(),
 655 -                    wikiutil.quoteWikinameURL(self.page_name),
 656 +                    self.urlName(),
 657                      urllib.quote_plus(page_needle.encode(config.charset), ''))
 658                  title = self.split_title(request)
 659                  if self.rev:
 660 @@ -922,7 +1014,10 @@
 661              request.write("<strong>%s</strong><br>" % _("You are not allowed to view this page."))
 662          else:
 663              # parse the text and send the page content
 664 -            self.send_page_content(request, Parser, body, format_args=pi_formatargs, do_cache=do_cache)
 665 +            request.clock.start('send_page_content')
 666 +            self.send_page_content(request, Parser, body,
 667 +                                   format_args=pi_formatargs, do_cache=do_cache)
 668 +            request.clock.stop('send_page_content')
 669  
 670              # check for pending footnotes
 671              if getattr(request, 'footnotes', None):
 672 @@ -1001,7 +1096,6 @@
 673          @param body: text of the wiki page
 674          @param needsupdate: if 1, force update of the cached compiled page
 675          """
 676 -        request.clock.start('send_page_content')
 677          formatter_name = self.getFormatterName()
 678  
 679          # if we should not or can not use caching
 680 @@ -1018,8 +1112,7 @@
 681          cache = caching.CacheEntry(request, arena, key)
 682          code = None
 683  
 684 -        if cache.needsUpdate(self._text_filename(),
 685 -                             self.getPagePath('attachments', check_create=0)):
 686 +        if cache.needsUpdate(self._text_filename(), self.getAttachmentPath()):
 687              needsupdate = 1
 688  
 689          # load cache
 690 @@ -1094,14 +1187,12 @@
 691          self.cache_mtime = cache.mtime()
 692  
 693          # TODO: move this into theme (currently used only by classic)
 694 -        qpage = wikiutil.quoteWikinameURL(self.page_name)
 695 +        qpage = self.urlName()
 696          url = "%s?action=refresh&amp;arena=Page.py&amp;key=%s" % (qpage, key)        
 697          link = wikiutil.link_tag(request, url, _("RefreshCache", formatted=False))
 698          date = self.request.user.getFormattedDateTime(cache.mtime())
 699          fragment = link + ' ' +  _('(cached %s)') % date
 700          self.request.add2footer('RefreshCache', fragment)
 701 -
 702 -        request.clock.stop('send_page_content')
 703          
 704      def _emptyPageText(self, request):
 705          """
 706 @@ -1125,17 +1216,20 @@
 707          @return: page revisions
 708          """
 709          revisions = []
 710 -        if self.page_name:
 711 -            rev_dir = self.getPagePath('revisions', check_create=0)
 712 -            if os.path.isdir(rev_dir):
 713 -                for rev in os.listdir(rev_dir):
 714 +        info = self.getInfo()
 715 +        path = info.get('path')
 716 +        if path:
 717 +            path = os.path.join(path, 'revisions')
 718 +            if os.path.isdir(path):
 719 +                for name in os.listdir(path):
 720                      try:
 721 -                        revint = int(rev)
 722 -                        revisions.append(revint)
 723 +                        number = int(name)
 724 +                        revisions.append(number)
 725                      except ValueError:
 726                          pass
 727                  revisions.sort()
 728                  revisions.reverse()
 729 +                
 730          return revisions
 731  
 732  
 733 @@ -1261,9 +1355,9 @@
 734              import wikiacl
 735              return wikiacl.AccessControlList(request)
 736          # mtime check for forked long running processes
 737 -        fn = self._text_filename()
 738 +        fn = self.getInfo().get('textfile')
 739          acl = None
 740 -        if os.path.exists(fn):
 741 +        if fn is not None:
 742              mtime = os.path.getmtime(fn)
 743          else:
 744              mtime = 0
 745 @@ -1325,19 +1419,17 @@
 746              if name.startswith('.') or name.startswith('#') or name == 'CVS':
 747                  continue
 748              
 749 -            # Filter deleted pages
 750 -            pagedir = os.path.join(dir, name)
 751 -            d, d, exists = self.get_rev(pagedir)
 752 -            if not exists:
 753 -                continue
 754 -            
 755              # Unquote - from this point name is Unicode
 756              name = wikiutil.unquoteWikiname(name)
 757 -            
 758 +
 759              # Filter meta-pages like editor backups
 760              if name.endswith(u'/MoinEditorBackup'):
 761                  continue           
 762 -            
 763 +
 764 +            # Filter deleted pages
 765 +            if not Page(self.request, name).exists():
 766 +                continue   
 767 +                        
 768              # Filter out page user may not read
 769              if user and not user.may.read(name):
 770                  continue  
 771 
 772 
 773 --- orig/MoinMoin/PageEditor.py
 774 +++ mod/MoinMoin/PageEditor.py
 775 @@ -8,7 +8,7 @@
 776  
 777  import os, time, codecs
 778  from MoinMoin import caching, config, user, util, wikiutil, error
 779 -from MoinMoin.Page import Page
 780 +from MoinMoin.Page import Page, _Page
 781  from MoinMoin.widget import html
 782  from MoinMoin.widget.dialog import Status
 783  from MoinMoin.logfile import editlog, eventlog
 784 @@ -78,7 +78,7 @@
 785  #############################################################################
 786  ### PageEditor - Edit pages
 787  #############################################################################
 788 -class PageEditor(Page):
 789 +class PageEditor(_Page):
 790      """Editor for a wiki page."""
 791  
 792      # exceptions for .saveText()
 793 @@ -112,7 +112,7 @@
 794          self._ = request.getText
 795          self.cfg = request.cfg
 796  
 797 -        Page.__init__(self, request, page_name, **keywords)
 798 +        _Page.__init__(self, request, page_name, **keywords)
 799  
 800          self.do_revision_backup = keywords.get('do_revision_backup', 1)
 801          self.do_editor_backup = keywords.get('do_editor_backup', 1)
 802 @@ -311,7 +311,7 @@
 803          # send form
 804          self.request.write('<form id="editor" method="post" action="%s/%s#preview">' % (
 805              self.request.getScriptname(),
 806 -            wikiutil.quoteWikinameURL(self.page_name),
 807 +            self.urlName() ,
 808              ))
 809  
 810          # yet another weird workaround for broken IE6 (it expands the text
 811 @@ -709,9 +709,11 @@
 812          """
 813          Copy a page from underlay directory to page directory
 814          """
 815 -        src = self.getPagePath(force_pagedir='underlay', check_create=0)
 816 -        dst = self.getPagePath(force_pagedir='standard', check_create=0)
 817 -        if src and dst and src != dst and os.path.exists(src):
 818 +        info = self.getInfo()
 819 +        if info.get('domain') == 'underlay':
 820 +            src = info('path')
 821 +            dst = os.path.join(self.request.cfg.data_dir, 'pages',
 822 +                               self.getStorageName())
 823              try:
 824                  os.rmdir(dst) # simply remove empty dst dirs
 825                  # XXX in fact, we should better remove anything we regard as an
 826 @@ -721,20 +723,23 @@
 827                  pass
 828              if not os.path.exists(dst):
 829                  filesys.copytree(src, dst)
 830 +                # Invalidate page info, it will be re created when needed.
 831 +                self._pageinfo = None
 832  
 833      def _get_pragmas(self, text):
 834          pragmas = {}
 835 -        for line in text.split('\n'):
 836 -            if not line or line[0] != '#':
 837 -                # end of pragmas
 838 -                break
 839 -            
 840 -            if len(line) > 1 and line[1] == '#':
 841 -                # a comment within pragmas
 842 -                continue
 843 -            
 844 -            verb, args = (line[1:]+' ').split(' ', 1)
 845 -            pragmas[verb.lower()] = args.strip()
 846 +        if text is not None:       
 847 +            for line in text.split('\n'):
 848 +                if not line or line[0] != '#':
 849 +                    # end of pragmas
 850 +                    break
 851 +
 852 +                if len(line) > 1 and line[1] == '#':
 853 +                    # a comment within pragmas
 854 +                    continue
 855 +
 856 +                verb, args = (line[1:]+' ').split(' ', 1)
 857 +                pragmas[verb.lower()] = args.strip()
 858              
 859          return pragmas
 860  
 861 @@ -749,8 +754,7 @@
 862          was_deprecated = self._get_pragmas(self.get_raw_body()).has_key("deprecated")
 863  
 864          self.copypage()
 865 -
 866 -        pagedir = self.getPagePath(check_create=0)
 867 +        pagedir = os.path.join(self.cfg.data_dir, 'pages', self.getStorageName())
 868          revdir = os.path.join(pagedir, 'revisions')
 869          cfn = os.path.join(pagedir,'current')
 870          clfn = os.path.join(pagedir,'current-locked')
 871 @@ -1032,7 +1036,7 @@
 872  
 873      def _filename(self):
 874          """get path and filename for edit-lock file"""
 875 -        return self.pageobj.getPagePath('edit-lock', isfile=1)
 876 +        return self.pageobj.getEditLockPath()
 877  
 878  
 879      def _readLockFile(self):
 880 @@ -1043,30 +1047,35 @@
 881          self.timestamp = 0
 882  
 883          if self.locktype:
 884 -            try:
 885 -                entry = editlog.EditLog(self.request, filename=self._filename()).next()
 886 -            except StopIteration:
 887 -                entry = None
 888 -                                                    
 889 -            if entry:
 890 -                self.owner = entry.userid or entry.addr
 891 -                self.owner_html = entry.getEditor(self.request)
 892 -                self.timestamp = wikiutil.version2timestamp(entry.ed_time_usecs)
 893 -
 894 +            filename = self._filename()
 895 +            if filename is not None:
 896 +                try:
 897 +                    entry = editlog.EditLog(self.request, filename=self._filename()).next()
 898 +                except StopIteration:
 899 +                    entry = None
 900 +
 901 +                if entry:
 902 +                    self.owner = entry.userid or entry.addr
 903 +                    self.owner_html = entry.getEditor(self.request)
 904 +                    self.timestamp = wikiutil.version2timestamp(entry.ed_time_usecs)
 905  
 906      def _writeLockFile(self):
 907          """Write new lock file."""
 908          self._deleteLockFile()
 909 -        try:
 910 -            editlog.EditLog(self.request, filename=self._filename()).add(
 911 -               self.request, wikiutil.timestamp2version(self.now), 0, "LOCK", self.page_name)
 912 -        except IOError:
 913 -            pass
 914 +        filename = self._filename()
 915 +        if filename is not None:
 916 +            try:
 917 +                editlog.EditLog(self.request, filename=self._filename()).add(
 918 +                    self.request, wikiutil.timestamp2version(self.now), 0, "LOCK", self.page_name)
 919 +            except IOError:
 920 +                pass
 921  
 922      def _deleteLockFile(self):
 923          """Delete the lock file unconditionally."""
 924 -        try:
 925 -            os.remove(self._filename())
 926 -        except OSError:
 927 -            pass
 928 +        filename = self._filename()
 929 +        if filename is not None:
 930 +            try:
 931 +                os.remove(filename)
 932 +            except OSError:
 933 +                pass
 934  
 935 
 936 
 937 --- orig/MoinMoin/action/AttachFile.py
 938 +++ mod/MoinMoin/action/AttachFile.py
 939 @@ -58,7 +58,7 @@
 940              filesys.makeDirs(attach_dir)
 941      else:
 942          # send file via CGI, from page storage area
 943 -        attach_dir = Page(request, pagename).getPagePath("attachments", check_create=create)
 944 +        attach_dir = Page(request, pagename).getAttachmentPath(repair=create)
 945  
 946      return attach_dir
 947  
 948 @@ -97,7 +97,8 @@
 949      """
 950      _ = request.getText
 951      attach_dir = getAttachDir(request, pagename)
 952 -    if not os.path.exists(attach_dir): return ''
 953 +    if attach_dir is None:
 954 +        return ''
 955  
 956      files = os.listdir(attach_dir)
 957      if not files: return ''
 958 @@ -122,7 +123,7 @@
 959  
 960      attach_dir = getAttachDir(request, pagename)
 961      files = []
 962 -    if os.path.isdir(attach_dir):
 963 +    if attach_dir and os.path.isdir(attach_dir):
 964          files = os.listdir(attach_dir)
 965      page = Page(request, pagename)
 966      # TODO: remove escape=0 in 1.4
 967 @@ -247,7 +248,7 @@
 968  
 969  def _get_files(request, pagename):
 970      attach_dir = getAttachDir(request, pagename)
 971 -    if os.path.isdir(attach_dir):
 972 +    if attach_dir and os.path.isdir(attach_dir):
 973          files = map(lambda a: a.decode(config.charset), os.listdir(attach_dir))
 974          files.sort()
 975          return files
 976 
 977 
 978 --- orig/MoinMoin/caching.py
 979 +++ mod/MoinMoin/caching.py
 980 @@ -28,7 +28,7 @@
 981                  os.chmod(self.arena_dir, 0777 & config.umask)
 982          else: # arena is in fact a page object
 983              cache_dir = None
 984 -            self.arena_dir = arena.getPagePath('cache', check_create=1)
 985 +            self.arena_dir = arena.getCachePath()
 986          self.key = key
 987  
 988      def _filename(self):
 989 
 990 
 991 --- orig/MoinMoin/logfile/editlog.py
 992 +++ mod/MoinMoin/logfile/editlog.py
 993 @@ -75,9 +75,9 @@
 994          if filename == None:
 995              rootpagename = kw.get('rootpagename', None)
 996              if rootpagename:
 997 -                filename = Page(request, rootpagename).getPagePath('edit-log', isfile=1)
 998 +                filename = Page(request, rootpagename).getEditLogPath()
 999              else:
1000 -                filename = request.rootpage.getPagePath('edit-log', isfile=1)
1001 +                filename = request.rootpage.getEditLogPath()
1002          LogFile.__init__(self, filename, buffer_size)
1003          self._NUM_FIELDS = 9
1004          self._usercache = {}
1005 
1006 
1007 --- orig/MoinMoin/logfile/eventlog.py
1008 +++ mod/MoinMoin/logfile/eventlog.py
1009 @@ -15,9 +15,9 @@
1010              rootpagename = kw.get('rootpagename', None)
1011              if rootpagename:
1012                  from MoinMoin.Page import Page
1013 -                filename = Page(request, rootpagename).getPagePath('event-log', isfile=1)
1014 +                filename = Page(request, rootpagename).getEventLogPath()
1015              else:
1016 -                filename = request.rootpage.getPagePath('event-log', isfile=1)
1017 +                filename = request.rootpage.getEventLogPath()
1018          LogFile.__init__(self, filename, buffer_size)
1019  
1020      def add(self, request, eventtype, values=None, add_http_info=1, mtime_usecs=None):
1021 
1022 
1023 --- orig/MoinMoin/request.py
1024 +++ mod/MoinMoin/request.py
1025 @@ -67,6 +67,7 @@
1026          self._known_actions = None
1027          self.sent_headers = 0
1028          self.user_headers = []
1029 +        self._pages = {}
1030  
1031          # check for some asses trying to use us as a proxy:
1032          self.forbidden = False
1033 @@ -969,7 +970,7 @@
1034          # Set Pragma for http 1.0 caches
1035          # See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-14.32
1036          self.setHttpHeader('Pragma: no-cache')
1037 -       
1038 +        
1039      def setCookie(self):
1040          """ Set cookie for the current user
1041          
1042 @@ -1056,6 +1057,7 @@
1043              del self.user
1044              del self.theme
1045              del self.dicts
1046 +            del self._pages
1047          except:
1048              pass
1049  
1050 
1051 
1052 --- orig/MoinMoin/server/standalone.py
1053 +++ mod/MoinMoin/server/standalone.py
1054 @@ -148,12 +148,14 @@
1055          if config.memoryProfile:
1056              config.memoryProfile.addRequest()
1057          try:
1058 -            req = RequestStandAlone(self)
1059 +            
1060              if config.hotshotProfile and moin_requests_done > 0:
1061                  # Don't profile the first request, its not interesting
1062                  # for long running process, and its very expensive.
1063 +                req = config.hotshotProfile.runcall(RequestStandAlone, self)
1064                  config.hotshotProfile.runcall(req.run)
1065              else:
1066 +                req = RequestStandAlone(self)
1067                  req.run()
1068          except socket.error, err:
1069              # Ignore certain errors
1070 
1071 
1072 --- orig/MoinMoin/theme/__init__.py
1073 +++ mod/MoinMoin/theme/__init__.py
1074 @@ -287,6 +287,7 @@
1075          @return: navibar html
1076          """
1077          request = self.request
1078 +        request.clock.start('navibar')
1079          found = {} # pages we found. prevent duplicates
1080          items = [] # navibar items
1081          item = u'<li class="%s">%s</li>'
1082 @@ -329,6 +330,7 @@
1083  %s
1084  </ul>
1085  ''' % items
1086 +        request.clock.stop('navibar')
1087          return html
1088   
1089      def get_icon(self, icon):
1090 @@ -929,6 +931,7 @@
1091  
1092          # Make new edit bar
1093          request = self.request
1094 +        request.clock.start('theme_editbar')
1095          _ = self.request.getText
1096          link = wikiutil.link_tag
1097          quotedname = wikiutil.quoteWikinameURL(page.page_name)
1098 @@ -963,6 +966,7 @@
1099          
1100          # cache for next call
1101          self._cache[cacheKey] = html
1102 +        request.clock.stop('theme_editbar')
1103          return html
1104  
1105      def startPage(self):
1106 
1107 
1108 --- orig/MoinMoin/wikiutil.py
1109 +++ mod/MoinMoin/wikiutil.py
1110 @@ -926,6 +926,7 @@
1111      @keyword body_attr: additional <body> attributes
1112      @keyword body_onload: additional "onload" JavaScript code
1113      """
1114 +    request.clock.start('send_title')
1115      from MoinMoin.Page import Page
1116      _ = request.getText
1117      pagename = keywords.get('pagename', '')
1118 @@ -1121,13 +1122,15 @@
1119          request.themedict = d
1120  
1121          # now call the theming code to do the rendering
1122 +        request.clock.start('theme_header')
1123          output.append(theme.header(d))
1124 +        request.clock.stop('theme_header')
1125      
1126      # emit it
1127      request.write(''.join(output))
1128      output = []
1129      request.flush()
1130 -
1131 +    request.clock.stop('send_title')
1132  
1133  def send_footer(request, pagename, **keywords):
1134      """
1135 @@ -1139,6 +1142,7 @@
1136      @keyword showpage: true, when link back to page is wanted (default: false)
1137      @keyword print_mode: true, when page is displayed in Print mode
1138      """
1139 +    request.clock.start('send_footer')
1140      d = request.themedict
1141      theme = request.theme
1142  
1143 @@ -1150,6 +1154,7 @@
1144          # This is used only by classic now, kill soon
1145          d['footer_fragments'] = request._footer_fragments
1146          request.write(theme.footer(d, **keywords))
1147 +    request.clock.stop('send_footer')
1148  
1149      
1150  ########################################################################

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] (2004-12-19 13:56:22, 41.1 KB) [[attachment:page-refactor.patch]]
 All files | Selected Files: delete move to page copy to page

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