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.You are not allowed to attach a file to this page.