changeset 1165:b9c91deadca4

Merge with main.
author Alexander Schremmer <alex AT alexanderweb DOT de>
date Sat, 12 Aug 2006 23:56:08 +0200
parents 7fd2894837e0 (current diff) 32c936ab469e (diff)
children b6dee77f7e67 b34b3fad8161
files
diffstat 6 files changed, 104 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/PageEditor.py	Sat Aug 12 12:47:35 2006 +0200
+++ b/MoinMoin/PageEditor.py	Sat Aug 12 23:56:08 2006 +0200
@@ -808,6 +808,9 @@
 
         self.copypage()
 
+        # remember conflict state
+        self.setConflict(wikiutil.containsConflictMarker(text))
+
         # Write always on the standard directory, never change the
         # underlay directory copy!
         pagedir = self.getPagePath(use_underlay=0, check_create=0)
--- a/MoinMoin/action/SyncPages.py	Sat Aug 12 12:47:35 2006 +0200
+++ b/MoinMoin/action/SyncPages.py	Sat Aug 12 23:56:08 2006 +0200
@@ -36,6 +36,9 @@
 
 
 def normalise_pagename(page_name, prefix):
+    """ Checks if the page_name starts with the prefix.
+        Returns None if it does not, otherwise strips the prefix.
+    """
     if prefix:
         if not page_name.startswith(prefix):
             return None
@@ -53,6 +56,13 @@
 class SyncPage(object):
     """ This class represents a page in one or two wiki(s). """
     def __init__(self, name, local_rev=None, remote_rev=None, local_name=None, remote_name=None):
+        """ Creates a SyncPage instance.
+            @param name: The canonical name of the page, without prefixes.
+            @param local_rev: The revision of the page in the local wiki.
+            @param remote_rev: The revision of the page in the remote wiki.
+            @param local_name: The page name of the page in the local wiki.
+            @param remote_name: The page name of the page in the remote wiki.
+        """
         self.name = name
         self.local_rev = local_rev
         self.remote_rev = remote_rev
@@ -65,12 +75,13 @@
         return repr("<Remote Page %r>" % unicode(self))
 
     def __unicode__(self):
-        return u"%s<%r:%r>" % (self.name, self.local_rev, self.remote_rev)
+        return u"%s[%s|%s]<%r:%r>" % (self.name, self.local_name, self.remote_name, self.local_rev, self.remote_rev)
 
     def __lt__(self, other):
         return self.name < other.name
 
     def __hash__(self):
+        """ Ensures that the hash value of this page only depends on the canonical name. """
         return hash(self.name)
 
     def __eq__(self, other):
@@ -79,6 +90,9 @@
         return self.name == other.name
 
     def add_missing_pagename(self, local, remote):
+        """ Checks if the particular concrete page names are unknown and fills
+            them in.
+        """
         if self.local_name is None:
             n_name = normalise_pagename(self.remote_name, remote.prefix)
             assert n_name is not None
@@ -91,10 +105,14 @@
         return self # makes using list comps easier
 
     def filter(cls, sp_list, func):
+        """ Returns all pages in sp_list that let func return True
+            for the canonical page name.
+        """
         return [x for x in sp_list if func(x.name)]
     filter = classmethod(filter)
 
     def merge(cls, local_list, remote_list):
+        """ Merges two lists of SyncPages into one, migrating attributes like the names. """
         # map page names to SyncPage objects :-)
         d = dict(zip(local_list, local_list))
         for sp in remote_list:
@@ -107,27 +125,33 @@
     merge = classmethod(merge)
 
     def is_only_local(self):
+        """ Is true if the page is only in the local wiki. """
         return not self.remote_rev
 
     def is_only_remote(self):
+        """ Is true if the page is only in the remote wiki. """
         return not self.local_rev
 
     def is_local_and_remote(self):
+        """ Is true if the page is in both wikis. """
         return self.local_rev and self.remote_rev
 
     def iter_local_only(cls, sp_list):
+        """ Iterates over all pages that are local only. """
         for x in sp_list:
             if x.is_only_local():
                 yield x
     iter_local_only = classmethod(iter_local_only)
 
     def iter_remote_only(cls, sp_list):
+        """ Iterates over all pages that are remote only. """
         for x in sp_list:
             if x.is_only_remote():
                 yield x
     iter_remote_only = classmethod(iter_remote_only)
 
     def iter_local_and_remote(cls, sp_list):
+        """ Iterates over all pages that are local and remote. """
         for x in sp_list:
             if x.is_local_and_remote():
                 yield x
@@ -193,7 +217,21 @@
 
     # Public methods
     def get_diff(self, pagename, from_rev, to_rev):
-        return str(self.connection.getDiff(pagename, from_rev, to_rev))
+        """ Returns the binary diff of the remote page named pagename, given
+            from_rev and to_rev. """
+        result = self.connection.getDiff(pagename, from_rev, to_rev)
+        if isinstance(result, xmlrpclib.Fault):
+            raise Exception(result)
+        result["diff"] = str(result["diff"]) # unmarshal Binary object
+        return result
+
+    def merge_diff(self, pagename, diff, local_rev, delta_remote_rev, last_remote_rev, interwiki_name):
+        """ Merges the diff into the page on the remote side. """
+        result = self.connection.mergeDiff(pagename, xmlrpclib.Binary(diff), local_rev, delta_remote_rev, last_remote_rev, interwiki_name)
+        print result
+        if isinstance(result, xmlrpclib.Fault):
+            raise Exception(result)
+        return result
 
     # Methods implementing the RemoteWiki interface
     def get_interwiki_name(self):
@@ -203,7 +241,8 @@
         return self.connection.interwikiName()[1]
 
     def get_pages(self):
-        pages = self.connection.getAllPagesEx({"include_revno": True, "include_deleted": True})
+        pages = self.connection.getAllPagesEx({"include_revno": True, "include_deleted": True,
+                                               "exclude_non_writable": True}) # XXX fix when all 3 directions are supported
         rpages = []
         for name, revno in pages:
             normalised_name = normalise_pagename(name, self.prefix)
@@ -252,12 +291,20 @@
 
 
 class ActionClass:
+    INFO, WARN, ERROR = range(3) # used for logging
+
     def __init__(self, pagename, request):
         self.request = request
         self.pagename = pagename
         self.page = Page(request, pagename)
+        self.status = []
+
+    def log_status(self, level, message):
+        """ Appends the message with a given importance level to the internal log. """
+        self.status.append((level, message))
 
     def parse_page(self):
+        """ Parses the parameter page and returns the read arguments. """
         options = {
             "remotePrefix": "",
             "localPrefix": "",
@@ -325,12 +372,16 @@
 
             self.sync(params, local, remote)
         except ActionStatus, e:
-            return self.page.send_page(self.request, msg=u'<p class="error">%s</p>\n' % (e.args[0], ))
+            msg = u'<p class="error">%s</p><p>%s</p>\n' % (e.args[0], repr(self.status))
+        else:
+            msg = u"%s<p>%s</p>" % (_("Syncronisation finished."), repr(self.status))
 
-        return self.page.send_page(self.request, msg=_("Syncronisation finished."))
+        # XXX append self.status to the job page
+        return self.page.send_page(self.request, msg=msg)
     
     def sync(self, params, local, remote):
         """ This method does the syncronisation work. """
+        _ = self.request.getText
 
         l_pages = local.get_pages()
         r_pages = remote.get_pages()
@@ -363,10 +414,14 @@
         # XXX handle deleted pages
         for rp in on_both_sides:
             # XXX add locking, acquire read-lock on rp
+            print "Processing %r" % rp
 
-            current_page = Page(self.request, local_pagename)
+            local_pagename = rp.local_name
+            current_page = PageEditor(self.request, local_pagename)
+            if wikiutil.containsConflictMarker(current_page.get_raw_body()):
+                self.log_status(ActionClass.WARN, _("Skipped page %(pagename)s because of a local unresolved conflict.") % {"pagename": local_pagename})
+                continue
             current_rev = current_page.get_real_rev()
-            local_pagename = rp.local_pagename
 
             tags = TagStore(current_page)
             matching_tags = tags.fetch(iwid_full=remote.iwid_full)
@@ -385,13 +440,16 @@
                 old_page = Page(self.request, local_pagename, rev=local_rev)
                 old_contents = old_page.get_raw_body_str()
 
-            diff_result = remote.get_diff(rp.remote_pagename, remote_rev, None)
+            self.log_status(ActionClass.INFO, _("Synchronising page %(pagename)s with remote page %(remotepagename)s ...") % {"pagename": local_pagename, "remotepagename": rp.remote_name})
+
+            diff_result = remote.get_diff(rp.remote_name, remote_rev, None)
             is_remote_conflict = diff_result["conflict"]
             assert diff_result["diffversion"] == 1
             diff = diff_result["diff"]
             current_remote_rev = diff_result["current"]
 
             if remote_rev is None: # set the remote_rev for the case without any tags
+                self.log_status(ActionClass.INFO, _("This is the first synchronisation between this page and the remote wiki."))
                 remote_rev = current_remote_rev
 
             new_contents = patch(old_contents, decompress(diff)).decode("utf-8")
@@ -403,15 +461,27 @@
             new_local_rev = current_rev + 1 # XXX commit first?
             local_full_iwid = packLine([local.get_iwid(), local.get_interwiki_name()])
             remote_full_iwid = packLine([remote.get_iwid(), remote.get_interwiki_name()])
-            # XXX add remote conflict handling
-            very_current_remote_rev = remote.merge_diff(rp.remote_pagename, compress(textdiff(new_contents, verynewtext)), new_local_rev, remote_rev, current_remote_rev, local_full_iwid)
-            tags.add(remote_wiki=remote_full_iwid, remote_rev=very_current_remote_rev, current_rev=new_local_rev)
+
+            diff = textdiff(new_contents.encode("utf-8"), verynewtext.encode("utf-8"))
+            very_current_remote_rev = remote.merge_diff(rp.remote_name, compress(diff), new_local_rev, remote_rev, current_remote_rev, local_full_iwid)
             comment = u"Local Merge - %r" % (remote.get_interwiki_name() or remote.get_iwid())
+
+            # XXX upgrade to write lock
             try:
                 current_page.saveText(verynewtext, current_rev, comment=comment)
             except PageEditor.EditConflict:
+                # XXX remote rollback needed
                 assert False, "You stumbled on a problem with the current storage system - I cannot lock pages"
+            tags.add(remote_wiki=remote_full_iwid, remote_rev=very_current_remote_rev, current_rev=new_local_rev)
+
+            if not wikiutil.containsConflictMarker(verynewtext):
+                self.log_status(ActionClass.INFO, _("Page successfully merged."))
+            else:
+                self.log_status(ActionClass.WARN, _("Page merged with conflicts."))
+
+            # XXX release lock
             # XXX untested
 
+
 def execute(pagename, request):
     ActionClass(pagename, request).render()
--- a/MoinMoin/action/edit.py	Sat Aug 12 12:47:35 2006 +0200
+++ b/MoinMoin/action/edit.py	Sat Aug 12 23:56:08 2006 +0200
@@ -135,8 +135,6 @@
     # Save new text
     else:
         try:
-            still_conflict = wikiutil.containsConflictMarker(savetext)
-            pg.setConflict(still_conflict)
             savemsg = pg.saveText(savetext, rev, trivial=trivial, comment=comment)
         except pg.EditConflict, e:
             msg = e.message
--- a/MoinMoin/wikisync.py	Sat Aug 12 12:47:35 2006 +0200
+++ b/MoinMoin/wikisync.py	Sat Aug 12 23:56:08 2006 +0200
@@ -27,6 +27,7 @@
         @param remote_rev: The revision number on the remote end.
         @param current_rev: The related local revision.
         """
+        assert isinstance(remote_wiki, str) and isinstance(remote_rev, int) and isinstance(current_rev, int)
         self.remote_wiki = remote_wiki
         self.remote_rev = remote_rev
         self.current_rev = current_rev
@@ -122,7 +123,7 @@
         self.commit()
 
     def fetch(self, iwid_full=None, iw_name=None):
-        assert iwid_full ^ iw_name
+        assert bool(iwid_full) ^ bool(iw_name)
         if iwid_full:
             iwid_full = unpackLine(iwid_full)
             if len(iwid_full) == 1:
--- a/MoinMoin/xmlrpc/__init__.py	Sat Aug 12 12:47:35 2006 +0200
+++ b/MoinMoin/xmlrpc/__init__.py	Sat Aug 12 23:56:08 2006 +0200
@@ -125,11 +125,14 @@
             # report exception back to server
             response = xmlrpclib.dumps(xmlrpclib.Fault(1, self._dump_exc()))
         else:
-            # wrap response in a singleton tuple
-            response = (response,)
-
-            # serialize it
-            response = xmlrpclib.dumps(response, methodresponse=1)
+            if isinstance(response, xmlrpclib.Fault):
+                response = xmlrpclib.dumps(response)
+            else:
+                # wrap response in a singleton tuple
+                response = (response,)
+    
+                # serialize it
+                response = xmlrpclib.dumps(response, methodresponse=1)
 
         self.request.emit_http_headers([
             "Content-Type: text/xml; charset=utf-8",
@@ -235,10 +238,12 @@
                 include_system:: set it to false if you do not want to see system pages
                 include_revno:: set it to True if you want to have lists with [pagename, revno]
                 include_deleted:: set it to True if you want to include deleted pages
+                exclude_non_writable:: do not include pages that the current user may not write to
         @rtype: list
         @return: a list of all pages.
         """
-        options = {"include_system": True, "include_revno": False, "include_deleted": False}
+        options = {"include_system": True, "include_revno": False, "include_deleted": False,
+                   "exclude_non_writable": False}
         if opts is not None:
             options.update(opts)
 
@@ -247,6 +252,9 @@
         else:
             filter = lambda name: True
 
+        if options["exclude_non_writable"]:
+            filter = lambda name, filter=filter: filter(name) and self.request.user.may.write(name)
+
         pagelist = self.request.rootpage.getPageList(filter=filter, exists=not options["include_deleted"])
         
         if options['include_revno']:
--- a/docs/CHANGES.aschremmer	Sat Aug 12 12:47:35 2006 +0200
+++ b/docs/CHANGES.aschremmer	Sat Aug 12 23:56:08 2006 +0200
@@ -86,7 +86,10 @@
          direction option, replaced "localMatch"/"remoteMatch" by "pageMatch".
          Store mtime for InterWiki list updates and detect changes based on it.
          Added support for localPrefix and remotePrefix.
-Week 32: Continued work on the merge logic, finished prefix handling.
+Week 32: Continued work on the merge logic, finished prefix handling. Added local conflict
+         detection in SyncPages. Added logging support to SyncPages. Refactored conflict flag
+         detection from the edit action into the PageEditor class. Enhanced XMLRPC server in Moin to allow
+         XMLRPC functions to return Fault instances.
 
 2006-07-18: the requested daily entry is missing here, see http://moinmoin.wikiwikiweb.de/GoogleSoc2006/BetterProgress
 2006-07-19: the requested daily entry is missing here, see http://moinmoin.wikiwikiweb.de/GoogleSoc2006/BetterProgress