Mercurial > moin > 1.9
changeset 985:eaae4bcf60f3
merged main
author | Thomas Waldmann <tw AT waldmann-edv DOT de> |
---|---|
date | Tue, 18 Jul 2006 11:48:53 +0200 |
parents | 311492a91530 (current diff) f56db9746839 (diff) |
children | 175695a510f5 65dc979761f0 |
files | |
diffstat | 8 files changed, 383 insertions(+), 11 deletions(-) [+] |
line wrap: on
line diff
--- a/MoinMoin/Page.py Tue Jul 18 11:34:23 2006 +0200 +++ b/MoinMoin/Page.py Tue Jul 18 11:48:53 2006 +0200 @@ -780,7 +780,7 @@ def get_raw_body(self): """ Load the raw markup from the page file. - @rtype: str + @rtype: unicode @return: raw page contents of this page """ if self._raw_body is None: @@ -806,7 +806,15 @@ file.close() return self._raw_body + + def get_raw_body_str(self): + """ Returns the raw markup from the page file, as a string. + @rtype: str + @return: raw page contents of this page + """ + return self.get_raw_body().encode("utf-8") + def set_raw_body(self, body, modified=0): """ Set the raw body text (prevents loading from disk).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/action/SyncPages.py Tue Jul 18 11:48:53 2006 +0200 @@ -0,0 +1,98 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - SyncPages action + + This action allows you to synchronise pages of two wikis. + + @copyright: 2006 MoinMoin:AlexanderSchremmer + @license: GNU GPL, see COPYING for details. +""" + +import os +import zipfile +import xmlrpclib +from datetime import datetime + +from MoinMoin import wikiutil, config, user +from MoinMoin.PageEditor import PageEditor +from MoinMoin.Page import Page +from MoinMoin.wikidicts import Dict + +class ActionStatus(Exception): pass + +class RemoteWiki(object): + """ This class should be the base for all implementations of remote wiki + classes. """ + def getInterwikiName(self): + """ Returns the interwiki name of the other wiki. """ + return NotImplemented + + def __repr__(self): + """ Returns a representation of the instance for debugging purposes. """ + return NotImplemented + +class MoinWiki(RemoteWiki): + def __init__(self, interwikiname): + wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, '%s:""' % (interwikiname, )) + self.wiki_url = wikiutil.mapURL(self.request, wikiurl) + self.valid = not wikitag_bad + self.xmlrpc_url = self.wiki_url + "?action=xmlrpc2" + self.connection = self.createConnection() + + def createConnection(self): + return xmlrpclib.ServerProxy(self.xmlrpc_url, allow_none=True) + + # Methods implementing the RemoteWiki interface + def getInterwikiName(self): + return self.connection.interwikiName() + + def __repr__(self): + return "<RemoteWiki wiki_url=%r valid=%r>" % (self.valid, self.wiki_url) + +class ActionClass: + def __init__(self, pagename, request): + self.request = request + self.pagename = pagename + self.page = Page(request, pagename) + + def parsePage(self): + defaults = { + "remotePrefix": "", + "localPrefix": "", + "remoteWiki": "" + } + + defaults.update(Dict(self.request, self.pagename).get_dict()) + return defaults + + def render(self): + """ Render action + + This action returns a wiki page with optional message, or + redirects to new page. + """ + _ = self.request.getText + + params = self.parsePage() + + 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.")) + + if not params["remoteWiki"]: + raise ActionStatus(_("Incorrect parameters. Please supply at least the ''remoteWiki'' parameter.")) + + remote = MoinWiki(params["remoteWiki"]) + + if not remote.valid: + raise ActionStatus(_("The ''remoteWiki'' is unknown.")) + + # ... + self.sync(params) + except ActionStatus, e: + return self.page.send_page(self.request, msg=u'<p class="error">%s</p>\n' % (e.args[0], )) + + return self.page.send_page(self.request, msg=_("Syncronisation finished.")) + +def execute(pagename, request): + ActionClass(pagename, request).render()
--- a/MoinMoin/action/__init__.py Tue Jul 18 11:34:23 2006 +0200 +++ b/MoinMoin/action/__init__.py Tue Jul 18 11:48:53 2006 +0200 @@ -20,7 +20,7 @@ actions_excluded, making and checking tickets, rendering some form, displaying errors and doing stuff after an action. - @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>, + @copyright: 2000-2004 by Jrgen Hermann <jh@web.de>, 2006 MoinMoin:ThomasWaldmann @license: GNU GPL, see COPYING for details. """ @@ -427,7 +427,7 @@ # Save new text else: try: - still_conflict = r"/!\ '''Edit conflict" in savetext + still_conflict = wikiutil.containsConflictMarker(savetext) pg.setConflict(still_conflict) savemsg = pg.saveText(savetext, rev, trivial=trivial, comment=comment) except pg.EditConflict, e:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/util/bdiff.py Tue Jul 18 11:48:53 2006 +0200 @@ -0,0 +1,93 @@ +""" + MoinMoin - Binary patching and diffing + + @copyright: 2005 Matt Mackall <mpm@selenic.com> + @copyright: 2006 MoinMoin:AlexanderSchremmer + + Algorithm taken from mercurial's mdiff.py + + @license: GNU GPL, see COPYING for details. +""" + +import zlib, difflib, struct + +BDIFF_PATT = ">lll" +BDIFF_PATT_SIZE = struct.calcsize(BDIFF_PATT) + +def compress(text): + return zlib.compress(text) # here we could tune the compression level + +def decompress(bin): + return zlib.decompress(bin) + +def diff(a, b): + """ Generates a binary diff of the passed strings. + Note that you can pass arrays of strings as well. + This might give you better results for text files. """ + if not a: + s = "".join(b) + return s and (struct.pack(BDIFF_PATT, 0, 0, len(s)) + s) + + bin = [] + la = lb = 0 + + p = [0] + for i in a: p.append(p[-1] + len(i)) + + for am, bm, size in difflib.SequenceMatcher(None, a, b).get_matching_blocks(): + s = "".join(b[lb:bm]) + if am > la or s: + bin.append(struct.pack(BDIFF_PATT, p[la], p[am], len(s)) + s) + la = am + size + lb = bm + size + + return "".join(bin) + +def textdiff(a, b): + """ A diff function optimised for text files. Works with binary files as well. """ + return diff(a.splitlines(1), b.splitlines(1)) + +def patchtext(bin): + """ Returns the new hunks that are contained in a binary diff.""" + pos = 0 + t = [] + while pos < len(bin): + p1, p2, l = struct.unpack(BDIFF_PATT, bin[pos:pos + BDIFF_PATT_SIZE]) + pos += BDIFF_PATT_SIZE + t.append(bin[pos:pos + l]) + pos += l + return "".join(t) + +def patch(a, bin): + """ Patches the string a with the binary patch bin. """ + c = last = pos = 0 + r = [] + + while pos < len(bin): + p1, p2, l = struct.unpack(BDIFF_PATT, bin[pos:pos + BDIFF_PATT_SIZE]) + pos += BDIFF_PATT_SIZE + r.append(a[last:p1]) + r.append(bin[pos:pos + l]) + pos += l + last = p2 + c += 1 + r.append(a[last:]) + + return "".join(r) + +def test(): + a = ("foo\n" * 30) + b = (" fao" * 30) + + a = file(r"C:\Dokumente und Einstellungen\Administrator\Eigene Dateien\Progra\Python\MoinMoin\moin-1.6-sync\MoinMoin\util\test.1").read() + b = file(r"C:\Dokumente und Einstellungen\Administrator\Eigene Dateien\Progra\Python\MoinMoin\moin-1.6-sync\MoinMoin\util\test.2").read() + a = a.splitlines(1) + b = b.splitlines(1) + + d = diff(a, b) + z = compress(d) + print `patchtext(d)` + print `d` + print "".join(b) == patch("".join(a), d) + print len(d), len(z) +
--- a/MoinMoin/wikidicts.py Tue Jul 18 11:34:23 2006 +0200 +++ b/MoinMoin/wikidicts.py Tue Jul 18 11:48:53 2006 +0200 @@ -59,6 +59,9 @@ def values(self): return self._dict.values() + + def get_dict(self): + return self._dict def has_key(self, key): return self._dict.has_key(key)
--- a/MoinMoin/wikiutil.py Tue Jul 18 11:34:23 2006 +0200 +++ b/MoinMoin/wikiutil.py Tue Jul 18 11:48:53 2006 +0200 @@ -2,7 +2,7 @@ """ MoinMoin - Wiki Utility Functions - @copyright: 2000 - 2004 by Jürgen Hermann <jh@web.de> + @copyright: 2000 - 2004 by Jrgen Hermann <jh@web.de> @license: GNU GPL, see COPYING for details. """ @@ -1445,6 +1445,9 @@ else: return "%s%s</a>" % (result, text) +def containsConflictMarker(text): + """ Returns true if there is a conflict marker in the text. """ + return "/!\ '''Edit conflict" in text def linediff(oldlines, newlines, **kw): """
--- a/MoinMoin/xmlrpc/__init__.py Tue Jul 18 11:34:23 2006 +0200 +++ b/MoinMoin/xmlrpc/__init__.py Tue Jul 18 11:48:53 2006 +0200 @@ -492,18 +492,151 @@ for hit in results.hits] def xmlrpc_getMoinVersion(self): + """ Returns a tuple of the MoinMoin version: + (project, release, revision) + """ from MoinMoin import version return (version.project, version.release, version.revision) + # authorization methods + + def xmlrpc_getAuthToken(self, username, password, *args): + """ Returns a token which can be used for authentication + in other XMLRPC calls. If the token is empty, the username + or the password were wrong. """ + u = user.User(self.request, name=username, password=password, auth_method='xmlrpc_gettoken') + if u.valid: + return u.id + else: + return "" + + def xmlrpc_applyAuthToken(self, auth_token): + """ Applies the auth token and thereby authenticates the user. """ + u = user.User(self.request, id=auth_token, auth_method='xmlrpc_applytoken') + if u.valid: + self.request.user = u + return "SUCCESS" + else: + return xmlrpclib.Fault("INVALID", "Invalid token.") + + def xmlrpc_getDiff(self, pagename, from_rev, to_rev): + """ Gets the binary difference between two page revisions. See MoinMoin:WikiSyncronisation. """ + from MoinMoin.util.bdiff import textdiff, compress + + pagename = self._instr(pagename) + # User may read page? + if not self.request.user.may.read(pagename): + return self.notAllowedFault() + + def allowed_rev_type(data): + if data is None: + return True + return isinstance(data, int) and data > 0 + + if not allowed_rev_type(from_rev): + return xmlrpclib.Fault("FROMREV_INVALID", "Incorrect type for from_rev.") + + if not allowed_rev_type(to_rev): + return xmlrpclib.Fault("TOREV_INVALID", "Incorrect type for to_rev.") + + currentpage = Page(self.request, pagename) + if not currentpage.exists(): + return xmlrpclib.Fault("NOT_EXIST", "Page does not exist.") + + revisions = currentpage.getRevList() + + if from_rev is not None and from_rev not in revisions: + return xmlrpclib.Fault("FROMREV_INVALID", "Unknown from_rev.") + if to_rev is not None and to_rev not in revisions: + return xmlrpclib.Fault("TOREV_INVALID", "Unknown to_rev.") + + # use lambda to defer execution in the next lines + if from_rev is None: + oldcontents = lambda: "" + else: + oldpage = Page(request, pagename, rev=from_rev) + oldcontents = lambda: oldpage.get_raw_body_str() + + if to_rev is None: + newcontents = lambda: currentpage.get_raw_body() + else: + newpage = Page(request, pagename, rev=to_rev) + newcontents = lambda: newpage.get_raw_body_str() + newrev = newpage.get_real_rev() + + if oldcontents() and oldpage.get_real_rev() == newpage.get_real_rev(): + return xmlrpclib.Fault("ALREADY_CURRENT", "There are no changes.") + + newcontents = newcontents() + conflict = wikiutil.containsConflictMarker(newcontents) + diffblob = xmlrpclib.Binary(compress(textdiff(oldcontents(), newcontents))) + + return {"conflict": conflict, "diff": diffblob, "diffversion": 1, "current": currentpage.get_real_rev()} + + def xmlrpc_interwikiName(self): + """ Returns the interwiki name of the current wiki. """ + name = self.request.cfg.interwikiname + if name is None: + return None + else: + return self._outstr(name) + + def xmlrpc_mergeChanges(self, pagename, diff, local_rev, delta_remote_rev, last_remote_rev, interwiki_name): + """ Merges a diff sent by the remote machine and returns the number of the new revision. + Additionally, this method tags the new revision. + + @param pagename: The pagename that is currently dealt with. + @param diff: The diff that can be applied to the version specified by delta_remote_rev. + @param local_rev: The revno of the page on the other wiki system, used for the tag. + @param delta_remote_rev: The revno that the diff is taken against. + @param last_remote_rev: The last revno of the page `pagename` that is known by the other wiki site. + @param interwiki_name: Used to build the interwiki tag. + """ + from MoinMoin.util.bdiff import decompress, patch + + pagename = self._instr(pagename) + + # User may read page? + if not self.request.user.may.read(pagename) or not self.request.user.may.write(pagename): + return self.notAllowedFault() + + # XXX add locking here! + + # current version of the page + currentpage = Page(self.request, pagename) + + if currentpage.get_real_rev() != last_remote_rev: + return xmlrpclib.Fault("LASTREV_INVALID", "The page was changed") + + if not currentpage.exists() and diff is None: + return xmlrpclib.Fault("NOT_EXIST", "The page does not exist and no diff was supplied.") + + # base revision used for the diff + basepage = Page(self.request, pagename, rev=delta_remote_rev) + + # generate the new page revision by applying the diff + newcontents = patch(basepage.get_raw_body_str(), decompress(str(diff))) + + # write page + # XXX ... + + # XXX add a tag (interwiki_name, local_rev, current rev) to the page + # XXX return current rev + # XXX finished + + # XXX BEGIN WARNING XXX # All xmlrpc_*Attachment* functions have to be considered as UNSTABLE API - # they are neither standard nor are they what we need when we have switched # attachments (1.5 style) to mimetype items (hopefully in 1.6). - # They are likely to get removed again when we remove AttachFile module. - # So use them on your own risk. + # They will be partly removed, esp. the semantics of the function "listAttachments" + # cannot be sensibly defined for items. + # If the first beta or more stable release of 1.6 will have new item semantics, + # we will remove the functions before it is released. def xmlrpc_listAttachments(self, pagename): """ Get all attachments associated with pagename + Deprecated. @param pagename: pagename (utf-8) @rtype: list
--- a/docs/CHANGES.aschremmer Tue Jul 18 11:34:23 2006 +0200 +++ b/docs/CHANGES.aschremmer Tue Jul 18 11:48:53 2006 +0200 @@ -6,27 +6,61 @@ ToDo: * Implement actual syncronisation. - * Implement a cross-site authentication system. + * Implement a cross-site authentication system, i.e. mainly an + identity storage. + * Clean up trailing whitespace. + * Add page locking. + * How about using unique IDs that just derive from the interwikiname? + * How to handle renames? New Features: * XMLRPC method to return the Moin version * XMLRPC multicall support * Conflict icon in RecentChanges + * XMLRPC Authentication System + * Binary Diffing + * XMLRPC method to get binary diffs + * Bugfixes (only stuff that is buggy in moin/1.6 main branch): - * ... + * Conflict resolution fixes. Other Changes: * Refactored conflict resolution and XMLRPC code. + * Enhanced API at some points. Developer notes: * ... +Do not forget to check the related wiki page: http://moinmoin.wikiwikiweb.de/WikiSyncronisation Diary ===== -Week 21: Basic Infrastructur setup (repos), initial talks to the mentor, started writing the design document, helped other students to get started, started evaluating Mercurial as a DVCS backend -Week 22: Tax forms, Fulfilled transcription request, written conflict icon support, refactored conflict handling, changed conflict icon, Added xmlrpc multicall support into the server and backported the client code from python 2.4 +Week 21: Basic Infrastructur setup (repos), + initial talks to the mentor, started writing the design document, + helped other students to get started +Week 22: Tax forms, Fulfilled transcription request, + written conflict icon support, refactored conflict handling, + changed conflict icon, + Added xmlrpc multicall support into the server and + backported the client code from python 2.4 +Week 23: Debian-Sprint in Extremadura, Spain. Initial thoughts about Mercurial as + a base for syncronisation. (See wiki) +Week 24: Evaluation of OpenID as a base for authentication, written local testing scripts +Week 25: Conference in Chile (FET 2006). +Week 26: Implementation of the XMLRPC authentication system, added binary + diffing (mainly taken from Mercurial, but had to merge 5 changesets, + remove some mercurial dependencies and document it. Currently, Mercurial + uses a module written in C to solve the problem, so the Python code + was not cared for anymore.) +Week 27: Europython, Geneva. +Week 28: Debian-Edu Developer Camp. Implemented getDiff XMLRPC method, added preliminary SyncPages action, + added interwikiName XMLRPC method, added mergeChanges XMLRPC method. Started analysis of the moinupdate + script written by Stefan Merten. - +Time plan +========= +In July and August, most parts of the implementation will be finished +from 07-10 to 07-14 and from 08-03 to 08-19. Between those time spans, there +are exams.