changeset 1119:397b97122ad9

General SyncPages refactoring: Fixed option handling again, refined semantics of options, introduced direction option, replaced "localMatch"/"remoteMatch" by "pageMatch".
author Alexander Schremmer <alex AT alexanderweb DOT de>
date Wed, 02 Aug 2006 01:12:42 +0200
parents 156d160b1dd9
children 72a208bfe579
files MoinMoin/action/SyncPages.py docs/CHANGES.aschremmer
diffstat 2 files changed, 94 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/action/SyncPages.py	Mon Jul 31 12:01:57 2006 +0200
+++ b/MoinMoin/action/SyncPages.py	Wed Aug 02 01:12:42 2006 +0200
@@ -27,36 +27,81 @@
 from MoinMoin.Page import Page
 from MoinMoin.wikidicts import Dict, Group
 
+# directions
+UP, DOWN, BOTH = range(3)
+directions_map = {"up": UP, "down": DOWN, "both": BOTH}
 
 class ActionStatus(Exception): pass
 
 class UnsupportedWikiException(Exception): pass
 
 # Move these classes to MoinMoin.wikisync
-class RemotePage(object):
+class SyncPage(object):
     """ This class represents a page in (another) wiki. """
-    def __init__(self, name, revno):
+    def __init__(self, name, local_rev=None, remote_rev=None):
         self.name = name
-        self.revno = revno
+        self.local_rev = local_rev
+        self.remote_rev = remote_rev
+        assert local_rev or remote_rev
 
     def __repr__(self):
         return repr("<Remote Page %r>" % unicode(self))
 
     def __unicode__(self):
-        return u"%s<%i>" % (self.name, self.revno)
+        return u"%s<%r:%r>" % (self.name, self.local_rev, self.remote_rev)
 
     def __lt__(self, other):
         return self.name < other.name
 
+    def __hash__(self):
+        return hash(self.name)
+
     def __eq__(self, other):
-        if not isinstance(other, RemotePage):
+        if not isinstance(other, SyncPage):
             return false
         return self.name == other.name
 
-    def filter(cls, rp_list, regex):
-        return [x for x in rp_list if regex.match(x.name)]
+    def filter(cls, sp_list, func):
+        return [x for x in sp_list if func(x.name)]
     filter = classmethod(filter)
 
+    def merge(cls, local_list, remote_list):
+        # map page names to SyncPage objects :-)
+        d = dict(zip(local_list, local_list))
+        for sp in remote_list:
+            if sp in d:
+                d[sp].remote_rev = sp.remote_rev
+            else:
+                d[sp] = sp
+        return d.keys()
+    merge = classmethod(merge)
+
+    def is_only_local(self):
+        return not self.remote_rev
+
+    def is_only_remote(self):
+        return not self.local_rev
+
+    def is_local_and_remote(self):
+        return self.local_rev and self.remote_rev
+
+    def iter_local_only(cls, sp_list):
+        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):
+        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):
+        for x in sp_list:
+            if x.is_local_and_remote():
+                yield x
+    iter_local_and_remote = classmethod(iter_local_and_remote)
 
 class RemoteWiki(object):
     """ This class should be the base for all implementations of remote wiki
@@ -71,7 +116,7 @@
         return NotImplemented
 
     def getPages(self):
-        """ Returns a list of RemotePage instances. """
+        """ Returns a list of SyncPage instances. """
         return NotImplemented
 
 
@@ -97,7 +142,7 @@
         if not self.is_anonymous and interwikiname != remote_interwikiname:
             raise UnsupportedWikiException(_("The remote wiki uses a different InterWiki name (%(remotename)s)"
                                              " internally than you specified (%(localname)s).") % {
-                "remotename": remote_interwikiname, "localname": interwikiname})
+                "remotename": wikiutil.escape(remote_interwikiname), "localname": wikiutil.escape(interwikiname)})
 
         if self.is_anonymous:
             self.iwid_full = packLine([remote_iwid])
@@ -113,7 +158,7 @@
 
     def getPages(self):
         pages = self.connection.getAllPagesEx({"include_revno": True, "include_deleted": True})
-        return [RemotePage(unicode(name), revno) for name, revno in pages]
+        return [SyncPage(unicode(name), remote_rev=revno) for name, revno in pages]
 
     def __repr__(self):
         return "<MoinRemoteWiki wiki_url=%r valid=%r>" % (self.wiki_url, self.valid)
@@ -128,17 +173,17 @@
         pages = []
         for group_pagename in group_list:
             pages.extend(Group(self.request, group_pagename).members())
-        return [self.createRemotePage(x) for x in pages]
+        return [self.createSyncPage(x) for x in pages]
 
-    def createRemotePage(self, page_name):
-        return RemotePage(page_name, Page(self.request, page_name).get_real_rev())
+    def createSyncPage(self, page_name):
+        return SyncPage(page_name, local_rev=Page(self.request, page_name).get_real_rev())
 
     # Methods implementing the RemoteWiki interface
     def getInterwikiName(self):
         return self.request.cfg.interwikiname
 
     def getPages(self):
-        return [self.createRemotePage(x) for x in self.request.rootpage.getPageList(exists=0)]
+        return [self.createSyncPage(x) for x in self.request.rootpage.getPageList(exists=0)]
 
     def __repr__(self):
         return "<MoinLocalWiki>"
@@ -155,10 +200,10 @@
             "remotePrefix": "",
             "localPrefix": "",
             "remoteWiki": "",
-            "localMatch": None,
-            "remoteMatch": None,
+            "pageMatch": None,
             "pageList": None,
             "groupList": None,
+            "direction": "foo", # is defaulted below
         }
 
         options.update(Dict(self.request, self.pagename).get_dict())
@@ -169,21 +214,25 @@
         if options["groupList"] is not None:
             options["groupList"] = unpackLine(options["groupList"], ",")
 
+        options["direction"] = directions_map.get(options["direction"], BOTH)
+
         return options
 
     def fix_params(self, params):
         """ Does some fixup on the parameters. """
 
-        # merge the pageList case into the remoteMatch case
+        # merge the pageList case into the pageMatch case
         if params["pageList"] is not None:
-            params["localMatch"] = params["remoteMatch"] = u'|'.join([r'^%s$' % re.escape(name)
-                                                                      for name in params["pageList"]])
+            params["pageMatch"] = u'|'.join([r'^%s$' % re.escape(name)
+                                             for name in params["pageList"]])
+            del params["pageList"]
 
-        if params["localMatch"] is not None:
-            params["localMatch"] = re.compile(params["localMatch"], re.U)
-        
-        if params["remoteMatch"] is not None:
-            params["remoteMatch"] = re.compile(params["remoteMatch"], re.U)
+        if params["pageMatch"] is not None:
+            params["pageMatch"] = re.compile(params["pageMatch"], re.U)
+
+        # we do not support matching or listing pages if there is a group of pages
+        if params["groupList"]:
+            params["pageMatch"] = None
 
         return params
 
@@ -196,7 +245,6 @@
 
         params = self.fix_params(self.parse_page())
 
-
         try:
             if not self.request.cfg.interwikiname:
                 raise ActionStatus(_("Please set an interwikiname in your wikiconfig (see HelpOnConfiguration) to be able to use this action."))
@@ -224,32 +272,32 @@
         
         r_pages = remote.getPages()
         l_pages = local.getPages()
-        print "Got %i local, %i remote pages" % (len(l_pages), len(r_pages))
-        if params["localMatch"]:
-            l_pages = RemotePage.filter(l_pages, params["localMatch"])
-        if params["remoteMatch"]:
-            print "Filtering remote pages using regex %r" % params["remoteMatch"].pattern
-            r_pages = RemotePage.filter(r_pages, params["remoteMatch"])
-        print "After filtering: Got %i local, %i remote pages" % (len(l_pages), len(r_pages))
 
         if params["groupList"]:
-            pages_from_groupList = local.getGroupItems(params["groupList"])
-            if not params["localMatch"]:
-                l_pages = pages_from_groupList
-            else:
-                l_pages += pages_from_groupList
+            pages_from_groupList = set(local.getGroupItems(params["groupList"]))
+            r_pages = SyncPage.filter(r_pages, pages_from_groupList.__contains__)
+            l_pages = SyncPage.filter(l_pages, pages_from_groupList.__contains__)
 
-        l_pages = set(l_pages)
-        r_pages = set(r_pages)
+        m_pages = SyncPage.merge(l_pages, r_pages)
+
+        print "Got %i local, %i remote pages, %i merged pages" % (len(l_pages), len(r_pages), len(m_pages))
         
-        # XXX this is not correct if matching is active
-        remote_but_not_local = r_pages - l_pages
-        local_but_not_remote = l_pages - r_pages
+        if params["pageMatch"]:
+            m_pages = SyncPage.filter(m_pages, params["pageMatch"].match)
+        print "After filtering: Got %i merges pages" % (len(m_pages), )
+
+        on_both_sides = list(SyncPage.iter_local_and_remote(m_pages))
+        remote_but_not_local = list(SyncPage.iter_remote_only(m_pages))
+        local_but_not_remote = list(SyncPage.iter_local_only(m_pages))
         
         # some initial test code
         r_new_pages = u", ".join([unicode(x) for x in remote_but_not_local])
         l_new_pages = u", ".join([unicode(x) for x in local_but_not_remote])
-        raise ActionStatus("These pages are in the remote wiki, but not local: " + r_new_pages + "<br>These pages are in the local wiki, but not in the remote one: " + l_new_pages)
+        raise ActionStatus("These pages are in the remote wiki, but not local: " + wikiutil.escape(r_new_pages) + "<br>These pages are in the local wiki, but not in the remote one: " + wikiutil.escape(l_new_pages))
+        #if params["direction"] in (DOWN, BOTH):
+        #    for rp in remote_but_not_local:
+                # XXX add locking, acquire read-lock on rp
+                
 
 
 def execute(pagename, request):
--- a/docs/CHANGES.aschremmer	Mon Jul 31 12:01:57 2006 +0200
+++ b/docs/CHANGES.aschremmer	Wed Aug 02 01:12:42 2006 +0200
@@ -79,7 +79,9 @@
 Week 30: Implemented IWID support, added function to generate random strings. Added support
          for editing the InterWikiMap in the wiki. Added locking to the PickleTagStore and the MetaDict classes. Added handling of
          various options and detection of anonymous wikis to the SyncPages action.
-Week 31: Load the IWID and the meta dict lazily.
+Week 31: Load the IWID and the meta dict lazily. Reworked RemotePage/SyncPage,
+         fixed option handling again, refined semantics of options, introduced
+         direction option, replaced "localMatch"/"remoteMatch" by "pageMatch".
 
 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