Extension:Rsync

From MediaWiki.org

Jump to: navigation, search
Manual on MediaWiki Extensions
List of MediaWiki Extensions
Rsync

Release status: experimental

Implementation User interface
Description
Author(s) Jean-Lou Dupont
Version See SVN ($Id: rsync.php 678 2007-08-17 15:32:06Z jeanlou.dupont $)
MediaWiki tested on 1.10 but probably works with a earlier versions
Download SVN
Hooks used

ArticleSaveComplete
ArticleDelete
ArticleDeleteComplete
SpecialMovepageAfterMove
FileUpload
AddNewAccount
ArticleProtectComplete
RecentChange_save

Contents

[edit] WARNING

This extension is work in progress.

[edit] Purpose

Provides a file based export of all the page changes. The directory containing the files can be used along with 'rsync' to provide backup & restore functionality.

[edit] Features

  • Page
    • Creation (done)
    • Update (done)
    • Delete (done)
    • Move (done)
    • Protection (done)
  • File
    • Upload
    • Re-upload
    • Delete
    • Move (not allowed - hence, nothing to implement)
  • User
    • Account creation
    • Account options update

[edit] Theory Of Operation

Page change events are trapped and the resulting new/updated pages are written to a specified filesystem directory. Trapping is done through the 'RecentChange_save' hook.

[edit] New page

File generated: RC-id-new.xml

[edit] Edit page

File generated: RC-id-edit.xml

[edit] Delete page

File generated: RC-id-delete.xml

[edit] Move page

File generated: RC-id-move.xml

[edit] Filename

Format: RC-id-type.xml

  • id: unique identifier generated in the context of the 'RecentChanges' table
  • type: 'new', 'edit' or 'log'
    • new --> new page
    • edit --> edit on a page
    • delete --> page deletion
    • log --> log entry

[edit] Implementation

[edit] New Page

  • Do complete export

[edit] Edit Page

  • Do complete export

[edit] Move Page

A move transaction is the page with the new title enclosed in an xml file WITH a new section 'source_title'.

  • Complete export with 'source_title' section

[edit] Delete Page

  • Export but don't 'writeRevision'

[edit] Page Restrictions

Needed to superclass 'WikiExporter' and 'XmlDumpWriter' classes.

  • Export but don't 'writeRevision'
  • Added 'restrictions' section to the XML dump

[edit] Usage Notes

Make sure that the dump directory is writable.

[edit] Dependancy

[edit] Installation

To install independantly from BizzWiki:

require_once('/extensions/StubManager.php');
require('/extensions/rsync/rsync_stub.php');

[edit] History

[edit] See Also

This extension is part of the BizzWiki Platform.

[edit] Code

*/
 
$wgExtensionCredits[rsync::thisType][] = array( 
        'name'    => rsync::thisName,
        'version' => StubManager::getRevisionId('$Id: rsync.php 678 2007-08-17 15:32:06Z jeanlou.dupont $'),
        'author'  => 'Jean-Lou Dupont',
        'description' => "Provides page changes in an export file format.", 
);
 
class rsync
{
        const thisType = 'other';
        const thisName = 'rsync';
 
        const defaultDir = '_backup';
 
        var $directory; 
        var $found;
 
        //
        var $rc;
        var $op; // current operation
        var $executeDeferredInRcHook;
 
        /**
         */
        public function __construct() 
        {
                $this->found = false;
 
                $this->op = null;
 
                // assume the default directory location
                $this->directory = self::defaultDir;
 
                // format the directory path.
                global $IP;
                $this->dir = $IP.'/'.$this->directory;
 
                rsync_operation::$dir = $this->dir;
 
                $this->executeDeferredInRcHook = false;
        }
 
        /**
                Handles article creation & update
 
                Creation and Update operations can not be discerned;
                they are handled both as 'edit'.
         */      
        public function hArticleSaveComplete( &$article, &$user, &$text, &$summary, $minor, 
                                                                                        $dontcare1, $dontcare2, &$flags )
        {
                if (!$this->found)
                        return true;
 
                $this->op = new rsync_operation(rsync_operation::action_edit,
                                                                                $article,
                                                                                WikiExporter::CURRENT,
                                                                                true,  // include last revision text
                                                                                $this->rc->mAttribs['rc_id'],
                                                                                $this->rc->mAttribs['rc_timestamp']                                                                                 
                                                                         );
 
                rsync_operations::add( $this->op );
                rsync_operations::execute();
 
                return true;
        }
 
        /**
                WARNING: If ArticleDelete hook fails, we might have some stranded resources
                e.g. temporary file
         */
        public function hArticleDelete( &$article, &$user, $reason )
        {
                $this->op = new rsync_operation(rsync_operation::action_delete,
                                                                                $article,
                                                                                WikiExporter::CURRENT,
                                                                                false, // don't include last revision text
                                                                                null,  // we don't know the id just yet
                                                                                null   // nor the timestamp
                                                                                );
                rsync_operations::add( $this->op );
                rsync_operations::execute();
 
                return true;
        }
        /**
                Handles article delete.
         */
        public function hArticleDeleteComplete( &$article, &$user, $reason )
        {
                $this->op->setIdTs(       $this->rc->mAttribs['rc_id'], 
                                                        $this->rc->mAttribs['rc_timestamp'] );
 
                rsync_operations::executeDeferred();         
                return true;
        }
 
        /**
                Handles article move.
 
                This hook is often called twice:
                - Once for the page
                - Once for the 'talk' page corresponding to 'page'
         */
        public function hSpecialMovepageAfterMove( &$sp, &$oldTitle, &$newTitle )
        {
                if (!$this->found)
                        return true;
 
                $this->op = new rsync_operation(rsync_operation::action_move,
                                                                                $newTitle,
                                                                                WikiExporter::CURRENT,
                                                                                true,  // include last revision text
                                                                                $this->rc->mAttribs['rc_id'],
                                                                                $this->rc->mAttribs['rc_timestamp']                                                                                 
                                                                         );
 
                rsync_operations::add( $this->op );
                $this->op->setSourceTitle( $oldTitle );
 
                rsync_operations::execute();
 
 
                return true;          
        }
 
        /**
                File Upload     
         */
        public function hFileUpload( &$img )
        {
                $this->op = new rsync_operation(rsync_operation::action_createfile,
                                                                                $article,
                                                                                WikiExporter::CURRENT,
                                                                                false, // do not include last revision text
                                                                                null,
                                                                                null                                                                           
                                                                         );
                rsync_operations::add( $this->op );
                rsync_operations::execute();
 
                $this->executeDeferredInRcHook = true;            
 
                return true;          
        }
 
        /**
         */
        #public function hUploadComplete( &$img ) {       return true;    }
 
        /**
                TBD
         */
        public function hAddNewAccount( &$user )
        {
 
                return true;          
        }
 
        /**
                Just send the 'page' details which contain the 'restrictions'
                aka 'protection' information.   
         */
        public function hArticleProtectComplete( &$article, &$user, &$limit, &$reason )
        {
                $this->op = new rsync_operation(rsync_operation::action_protect,
                                                                                $article,
                                                                                WikiExporter::CURRENT,
                                                                                false, // do not include last revision text
                                                                                null,
                                                                                null                                                                           
                                                                         );
 
                rsync_operations::add( $this->op );
                rsync_operations::execute();
 
                $this->executeDeferredInRcHook = true;            
 
                return true;
        }
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%   
 
        /**
                Just grab the essential parameters we need to complete the transaction.
         */
        public function hRecentChange_save( &$rc )
        {
                $this->rc = $rc;
 
                $this->found = true;
 
                if ($this->executeDeferredInRcHook)
                {
                        $this->op->setIdTs(       $this->rc->mAttribs['rc_id'], 
                                                                $this->rc->mAttribs['rc_timestamp'] );
 
                        rsync_operations::executeDeferred();                 
                }
 
                return true;          
        }
 
} // end class
 
class rsync_operations
{
        static $liste;
        static $dListe;
        static $id = null;
        static $timestamp = null;
 
        public static function add( &$op ) { self::$liste[] = $op;  }
        public static function addDeferred( &$op ) { self::$dListe[] = $op; }
 
        public static function execute()
        {
                if (!empty( self::$liste ))      
                        foreach( self::$liste as &$op )
                        {
                                $d = $op->getDeferralState();
                                $op->execute();
 
                                if ($d)     
                                        self::addDeferred( $op );
                        }
        }
 
        public static function executeDeferred()
        {
                if (!empty( self::$dListe ))     
                        foreach( self::$dListe as &$op )
                                $op->executeDeferred();
        }
 
} // end class
 
/**               ************************************************************
                Follows is a class that defines an 'rsync' export operation.
 */
 
class rsync_operation
{
        //
        static $dir;
 
                // Constants
        const action_none       = 0;
        const action_create     = 1; // TBD
 
                // page related
        const action_edit       = 2;
        const action_delete     = 3;
        const action_move       = 4;
        const action_protect    = 5;
 
                // file related
        const action_createfile = 6;
        const action_deletefile = 7;
 
        // Commit Operation parameters
        var $includeRevision;
        var $deferralRequired;
 
        var $id;
        var $timestamp;
 
        var $action;
        var $ns;
        var $titre;
 
        var $sourceTitle;     // for move action
 
        var $isFilenameTemp;  
        var $filename;
        var $history;         // current or full
 
        var $text;
 
        public function __construct( $action, &$object, $history, $includeRevision, $id, $ts )
        {
                if ( $object instanceof Article )
                        $title = $object->mTitle;
                else
                        $title = $object;
 
                $this->ns = $title->getNamespace();
                $this->titre = $title->getText();
 
                $this->action = $action;
                $this->history = $history;
                $this->includeRevision = $includeRevision;
                $this->deferralRequired = $this->getDeferralState( );
 
                $this->id = $id;
                $this->timestamp = $ts;
 
                // will get filled later.             
                $this->filename = null;           // gets filled during 'updateList'
                $this->isFilenameTemp = null;
 
                $this->sourceTitle = null;
        }
        public function setIdTs( $id, $ts ) { $this->id = $id; $this->timestamp = $ts; }
        public function setSourceTitle( &$t ) { $this->sourceTitle = $t; }
 
        /**
                Delete action requires deferral
         */
        public function getDeferralState( )
        {
                if ($this->action == self::action_delete)      
                        return true;
 
                if ($this->action == self::action_protect)     
                        return true;
 
                return false;
        }
 
        /**
                Page-rcid-action-namespace.xml
         */
        protected function generateFilename( )
        {
                if ($this->id === null)
                {
                        $this->isFilenameTemp = true;
                        $this->filename = tempnam( self::$dir, 'rsync_' );
                        return;
                }
 
                $this->filename = self::$dir."/Page-".$this->id.'-'.$this->action.'-'.$this->ns.'.xml';
        }
 
        public function execute()
        {
                $this->generateFilename();
 
/*                
                switch( $this->action )      
                {
                        case self::action_edit:
 
                        case self::action_delete:
 
                }
*/
                $this->export();         
        }
        /**
                Deferred execution consists in renaming the 
                temporary export file.
         */
        public function executeDeferred()
        {
                $tempName = $this->filename;
 
                // generate a permanent filename
                $this->generateFilename();
 
                self::rename( $tempName /*source*/, $this->filename /*target*/ );
        }
 
        protected function rename( &$source, &$target )
        {
                $r = rename( $source, $target );
                if ($r === false)
                        throw new MWException();
        }
        /**
                This function uses MediaWiki's 'WikiExporter' class.
         */
        private function export( )
        {
                $dump = new DumpFileOutput( $this->filename );
 
                $db = wfGetDB( DB_SLAVE );
                $exporter = new WikiExporterEx( $db, $this->history );
 
                // used for 'move' operation
                if (!empty( $this->sourceTitle ))
                        $exporter->setSourceTitle( $this->sourceTitle );
 
                $exporter->setOutputSink( $dump );
                $exporter->includeRevision( $this->includeRevision );
 
                $exporter->openStream();
 
                $title = Title::makeTitle( $this->ns, $this->titre );
                if( is_null( $title ) ) return;
 
                $exporter->setPageTitle( $title );
 
                $exporter->pageByTitle( $title );
 
                $exporter->closeStream();
        }
 
} // end class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
        Class definition can be found in includes/Export.php
 */
class XmlDumpWriterEx extends XmlDumpWriter
{
        var $pageTitle;
        var $sourceTitle;
        var $includeRevision;
 
        function openPage( $row )
        {
                $out = parent::openPage( $row );
 
                if ($this->pageTitle instanceof Title)
                {
                        $this->pageTitle->loadRestrictions();
                        if (!empty( $this->pageTitle->mRestrictions ))
                                $out .= $this->getRestrictionsSection(       $this->pageTitle->mRestrictions, 
                                                                                                                $this->pageTitle->mRestrictionsExpiry,
                                                                                                                $this->pageTitle->mCascadeRestriction
                                                                                                                 );
                }
 
                if (is_a( $this->sourceTitle, 'Title'))
                        $out .= $this->getSourceTitleSection();
 
                return $out;
        }
        function getRestrictionsSection( &$restrictions, $expiry, $cascading )
        {
                $result = "<restrictions>\n";
                foreach( $restrictions as $restrictionType => &$levels )
                        foreach( $levels as $level)
                                $result .= "    <restriction type='".$restrictionType."' level='".$level.
                                                        "' expiry='".$expiry."' cascading='".$cascading."' />\n";
 
                $result .= "</restrictions>\n";
 
                return $result;
        }
        function getSourceTitleSection()
        {
                return "<source_title>".Namespace::getCanonicalName( $this->sourceTitle->getNamespace() ).
                                ":".$this->sourceTitle->getText()."</source_title>\n";  
        }
        function writeRevision( &$row )
        {
                if (!$this->includeRevision)
                        return null;
                return parent::writeRevision( $row );
        }
} // end class
 
class WikiExporterEx extends WikiExporter
{
        public function __construct( &$db, $history = WikiExporter::CURRENT,
                        $buffer = WikiExporter::BUFFER, $text = WikiExporter::TEXT )   
        {
                parent::__construct( $db, $history, $buffer, $text );     
                $this->writer = new XmlDumpWriterEx();
                $this->writer->includeRevision = true;
        }                 
        public function setPageTitle( &$title )        
        {
                $this->writer->pageTitle = $title;    
        }
        /**
                The source title is used in 'move' operations.
         */
        public function setSourceTitle( &$title )
        {
                $this->writer->sourceTitle = $title;  
        }
        /**
                It is helpful not to include the full revision text sometimes.
         */
        public function includeRevision( &$enable )
        {
                $this->writer->includeRevision = $enable;
        }
} // end class
 
//
Personal tools