BugTrack2/103

実装案 by Cue

弄ってみました。長くてすいません。*1



コンセプト

  1. バックアップデータ構造を変えても従来のデータを捨てずに移行できる事。また必要に応じて戻せる事。
  2. データ構造を隠蔽することで、lib/backup.phpを書き換えれば別のデータ構造に移行できるようにする事。
  3. データ構造を移行する際にメモリ不足やタイムアウトが原因で移行を妨げない事
  4. バックアップデータの日時は暫定的に差分のファイルが持つタイムスタンプを使用(このBugTrackではタイムスタンプ管理の問題に深入りしたくないため)

plugin/backup.inc.php

  1. lib/backup.phpの仕様変更に対応する部分の書き換え
  2. 世代指定の方法が変わった事に対するチェック強化
  3. コンバート呼出しのコード追加

ディレクトリ管理仕様のlib/backup.php

  1. ディレクトリ内のファイルが更新されてもディレクトリの最終更新日時が変更されないファイルシステムへの対応
  2. ファイル名にタイムスタンプを使うので既存データに同一時刻があった場合1秒加算する(別の解決策を提案して頂いたので変更するかもしれませんが、可能ならば例外処理は入れたくないところです)

従来型データ用のlib/backup.php

  1. 変更前のバックアップシステムと比較して速度や必要リソースで劣らない事。

最初はlib/backup.php, plugin/backup.inc.phpを両方入れ替えることでディレクトリ管理に移行する方向で考えていましたが、従来のbackup.phpにも最小限の安全性を加えようとしていくうちにずるずる今の形になってしまっています。

実装していて気付いた点など

  1. $maxageは正の整数を期待しているがノーチェック
    • 暫定的に0以下でバックアップ無しの動作に(世代無制限の方が正しいかもしれない)
  2. バックアップリストの配列が1からスタート
    • backup.inc.php側で処理すれば0オリジンで構わないので変更してしまいました
  3. 標準データ形式をどこまでサポートするか
    • 標準データ形式はコンバート用の一時ファイルになっています(ネイティブ形式以外は一覧が見られない等々)
  4. データコンバート処理
    • コンバート中にタイムアウトが発生した場合も不完全ながらレジューム可能
      • 処理経過をコンバートするファイルの有無に頼っている
      • 変換前と後のファイルが同時に存在する場合は途中で中断した場所とみなしてNoticeを出してその処理を飛ばす
    • メモリが少ない状況でもコンバート処理だけは可能な限り動作させる
    • 既存データに同一タイムスタンプがあった場合、1秒加算させている
      • 今はディレクトリ管理のみ。既存の形式でもコンバートしないと一覧で選択できない(対応急務)
        とりあえず何とかするには、(gz圧縮形式→)標準データ形式→backup.php差換え→ディレクトリ管理→標準データ形式→backup.php差換え(→gz圧縮形式)でお願いします(省メモリ化で手間取ってます)

        ディレクトリ管理のインポート時と従来データ用のエクスポート時に働きます。
  1. 必要メモリ
    • 既存のバックアップ形式については速度優先で全データを読み込んでstatic変数に貯めて処理している
      • 省メモリにも変えられるが、ファイルを2回スキャンしなければならない
  2. Noticeの出力
    • die_messageで処理を中断するほどではないが、データ不整合等を検出した場合に何らかの警告を出す統一した処理が欲しい
      • とりあえずerror_log($msg, 0)で出力してみる(これが正しいかは不明)

未実装な点


コメント



差分と差し替えファイル

lib/file.php の差分

--- /lib/file.php	Sun Jul 03 23:16:24 2005
+++ /lib/file.php	Mon Sep 12 00:26:52 2005
@@ -36,14 +36,14 @@
 
 	$postdata = make_str_rules($postdata);
 
+	// Create backup
+	make_backup($page, $postdata == ''); // Is $postdata null?
+
 	// Create and write diff
 	$oldpostdata = is_page($page) ? join('', get_source($page)) : '';
 	$diffdata    = do_diff($oldpostdata, $postdata);
 	file_write(DIFF_DIR, $page, $diffdata);
 
-	// Create backup
-	make_backup($page, $postdata == ''); // Is $postdata null?
-
 	// Create wiki text
 	file_write(DATA_DIR, $page, $postdata, $notimestamp);
 

plugin/backup.inc.php の差分

--- /plugin/backup.inc.php	Sun May 08 01:21:52 2005
+++ /plugin/backup.inc.php	Sat Sep 17 20:32:36 2005
@@ -9,60 +9,76 @@
 
 function plugin_backup_action()
 {
-	global $vars, $do_backup, $hr;
-	global $_msg_backuplist, $_msg_diff, $_msg_nowdiff, $_msg_source, $_msg_backup;
-	global $_msg_view, $_msg_goto, $_msg_deleted;
-	global $_title_backupdiff, $_title_backupnowdiff, $_title_backupsource;
-	global $_title_backup, $_title_pagebackuplist, $_title_backuplist;
+	global $vars, $do_backup;
+	global $_title_pagebackuplist, $_title_backuplist;
 
 	if (! $do_backup) return;
 
 	$page = isset($vars['page']) ? $vars['page']  : '';
+	$action = isset($vars['action']) ? $vars['action'] : '';
+
+	if (preg_match('/^(?:im|ex)port$/', $action)) return plugin_backup_convert($action);
 	if ($page == '') return array('msg'=>$_title_backuplist, 'body'=>plugin_backup_get_list_all());
 
 	check_readable($page, true, true);
-	$s_page = htmlspecialchars($page);
-	$r_page = rawurlencode($page);
-
-	$action = isset($vars['action']) ? $vars['action'] : '';
-	if ($action == 'delete') return plugin_backup_delete($page);
 
-	$s_action = $r_action = '';
-	if ($action != '') {
-		$s_action = htmlspecialchars($action);
-		$r_action = rawurlencode($action);
+	$s_utime = NULL;
+	if (isset($vars['age']) && is_numeric($vars['age']) &&
+		($vars['age'] += 0) > 0 && is_int($vars['age'])) {
+			$s_utime = $vars['age'];
 	}
 
-	$s_age  = (isset($vars['age']) && is_numeric($vars['age'])) ? $vars['age'] : 0;
-	if ($s_age == 0) return array( 'msg'=>$_title_pagebackuplist, 'body'=>plugin_backup_get_list($page));
+	if ($action == 'delete') return plugin_backup_delete($page, $s_utime);
+
+	if (isset($s_utime)) return plugin_backup_get_detail($page, $s_utime, $action);
+
+	return array( 'msg'=>$_title_pagebackuplist, 'body'=>plugin_backup_get_list($page));
+}
+
+function plugin_backup_get_detail($page, $s_utime, $action)
+{
+	global $hr, $_msg_view, $_msg_goto, $_msg_deleted, $_msg_nobackup;
+	global $_msg_backuplist, $_msg_diff, $_msg_nowdiff, $_msg_source, $_msg_backup;
+	global $_title_backupdiff, $_title_backupnowdiff, $_title_backupsource, $_title_backup;
+
+	$s_page = htmlspecialchars($page);
+	$r_page = rawurlencode($page);
 
+	$s_action = htmlspecialchars($action);
+	$r_action = rawurlencode($action);
+
 	$script = get_script_uri();
 
 	$body  = '<ul>' . "\n";
 	$body .= ' <li><a href="' . $script . '?cmd=backup">' . $_msg_backuplist . '</a></li>' ."\n";
 
-	$href    = $script . '?cmd=backup&amp;page=' . $r_page . '&amp;age=' . $s_age;
+	$href    = $script . '?cmd=backup&amp;page=' . $r_page . '&amp;age=' . $s_utime;
 	$is_page = is_page($page);
 
-	if ($is_page && $action != 'diff')
-		$body .= ' <li>' . str_replace('$1', '<a href="' . $href .
-			'&amp;action=diff">' . $_msg_diff . '</a>',
-			$_msg_view) . '</li>' . "\n";
-
-	if ($is_page && $action != 'nowdiff')
-		$body .= ' <li>' . str_replace('$1', '<a href="' . $href .
-			'&amp;action=nowdiff">' . $_msg_nowdiff . '</a>',
-			$_msg_view) . '</li>' . "\n";
-
-	if ($action != 'source')
-		$body .= ' <li>' . str_replace('$1', '<a href="' . $href .
-			'&amp;action=source">' . $_msg_source . '</a>',
-			$_msg_view) . '</li>' . "\n";
-
-	if ($action)
-		$body .= ' <li>' . str_replace('$1', '<a href="' . $href .
-			'">' . $_msg_backup . '</a>',
-			$_msg_view) . '</li>' . "\n";
+	$backups = get_backup($page);
+	$s_age = array_search($s_utime, $backups);
+
+	if (is_int($s_age)) {
+		if ($is_page && $action != 'diff')
+			$body .= ' <li>' . str_replace('$1', '<a href="' . $href .
+				'&amp;action=diff">' . $_msg_diff . '</a>',
+				$_msg_view) . '</li>' . "\n";
+
+		if ($is_page && $action != 'nowdiff')
+			$body .= ' <li>' . str_replace('$1', '<a href="' . $href .
+				'&amp;action=nowdiff">' . $_msg_nowdiff . '</a>',
+				$_msg_view) . '</li>' . "\n";
+
+		if ($action != 'source')
+			$body .= ' <li>' . str_replace('$1', '<a href="' . $href .
+				'&amp;action=source">' . $_msg_source . '</a>',
+				$_msg_view) . '</li>' . "\n";
+
+		if ($action)
+			$body .= ' <li>' . str_replace('$1', '<a href="' . $href .
+				'">' . $_msg_backup . '</a>',
+				$_msg_view) . '</li>' . "\n";
+	}
 
 	if ($is_page) {
 		$body .= ' <li>' . str_replace('$1',
@@ -72,69 +88,71 @@
 		$body .= ' <li>' . str_replace('$1', $s_page, $_msg_deleted) . "\n";
 	}
 
-	$backups = get_backup($page);
 	if (! empty($backups)) {
-		$body .= '  <ul>' . "\n";
-		foreach($backups as $age => $val) {
-			$date = format_date($val['time'], TRUE);
-			$body .= ($age == $s_age) ?
-				'   <li><em>' . $age . ' ' . $date . '</em></li>' . "\n" :
-				'   <li><a href="' . $script . '?cmd=backup&amp;action=' .
-				$r_action . '&amp;page=' . $r_page . '&amp;age=' . $age .
-				'">' . $age . ' ' . $date . '</a></li>' . "\n";
+		$body .= '  <ol>' . "\n";
+		foreach($backups as $age=>$utime) {
+			$date = format_date($utime, TRUE);
+			if ($age == $s_age) {
+				$body .= '   <li><em>' . $date . '</em></li>' . "\n";
+			} else {
+				$body .= '   <li><a href="' . $script . '?cmd=backup&amp;action=' .
+					$r_action . '&amp;page=' . $r_page . '&amp;age=' . $utime .
+					'">' . $date . '</a></li>' . "\n";
+			}
 		}
-		$body .= '  </ul>' . "\n";
+		$body .= '  </ol>' . "\n";
 	}
 	$body .= ' </li>' . "\n";
 	$body .= '</ul>'  . "\n";
 
-	if ($action == 'diff') {
+	if (!is_int($s_age)) {
+		$title = str_replace('(No.$2)', '', $_title_backup);
+		$body .= str_replace('$1', format_date($s_utime, TRUE), $_msg_nobackup);
+	} else if ($action == 'diff') {
 		$title = & $_title_backupdiff;
-		$old = ($s_age > 1) ? join('', $backups[$s_age - 1]['data']) : '';
-		$cur = join('', $backups[$s_age]['data']);
+		$old = $s_age > 0 ? get_backup($page, $backups[$s_age-1]) : '';
+		$cur = get_backup($page, $s_utime);
 		$body .= plugin_backup_diff(do_diff($old, $cur));
-	} else if ($s_action == 'nowdiff') {
+	} else if ($action == 'nowdiff') {
 		$title = & $_title_backupnowdiff;
-		$old = join('', $backups[$s_age]['data']);
-		$cur = join('', get_source($page));
+		$old = get_backup($page, $s_utime);
+		$cur = implode('', get_source($page));
 		$body .= plugin_backup_diff(do_diff($old, $cur));
-	} else if ($s_action == 'source') {
+	} else if ($action == 'source') {
 		$title = & $_title_backupsource;
-		$body .= '<pre>' . htmlspecialchars(join('', $backups[$s_age]['data'])) .
-			'</pre>' . "\n";
+		$body .= '<pre>' . htmlspecialchars(get_backup($page, $s_utime)) . '</pre>' . "\n";
 	} else {
 		if (PLUGIN_BACKUP_DISABLE_BACKUP_RENDERING) {
 			die_message('This feature is prohibited');
 		} else {
 			$title = & $_title_backup;
 			$body .= $hr . "\n" .
-				drop_submit(convert_html($backups[$s_age]['data']));
+				drop_submit(convert_html(get_backup($page, $s_utime)));
 		}
 	}
 
-	return array('msg'=>str_replace('$2', $s_age, $title), 'body'=>$body);
+	return array('msg'=>str_replace('$2', $s_age+1, $title), 'body'=>$body);
 }
 
 // Delete backup
-function plugin_backup_delete($page)
+function plugin_backup_delete($page, $s_utime)
 {
 	global $vars, $_title_backup_delete, $_title_pagebackuplist, $_msg_backup_deleted;
 	global $_msg_backup_adminpass, $_btn_delete, $_msg_invalidpass;
 
-	if (! _backup_file_exists($page))
+	if (! backup_file_exists($page))
 		return array('msg'=>$_title_pagebackuplist, 'body'=>plugin_backup_get_list($page)); // Say "is not found"
 
 	$body = '';
 	if (isset($vars['pass'])) {
 		if (pkwk_login($vars['pass'])) {
-			_backup_delete($page);
-			return array(
-				'msg'  => $_title_backup_delete,
-				'body' => str_replace('$1', make_pagelink($page), $_msg_backup_deleted)
-			);
-		} else {
-			$body = '<p><strong>' . $_msg_invalidpass . '</strong></p>' . "\n";
+			if(backup_delete($page, $s_utime))
+				$body =  str_replace('$1', make_pagelink($page), $_msg_backup_deleted);
+			else
+				$body = '削除失敗';
+			return array('msg'  => $_title_backup_delete, 'body' => $body);
 		}
+		$body = '<p><strong>' . $_msg_invalidpass . '</strong></p>' . "\n";
 	}
 
 	$script = get_script_uri();
@@ -145,6 +163,7 @@
  <div>
   <input type="hidden"   name="cmd"    value="backup" />
   <input type="hidden"   name="page"   value="$s_page" />
+  <input type="hidden"   name="age"    value="$s_utime" />
   <input type="hidden"   name="action" value="delete" />
   <input type="password" name="pass"   size="12" />
   <input type="submit"   name="ok"     value="$_btn_delete" />
@@ -161,7 +180,7 @@
 	$str = htmlspecialchars($str);
 	$str = preg_replace('/^(\-)(.*)$/m', '<span class="diff_removed"> $2</span>', $str);
 	$str = preg_replace('/^(\+)(.*)$/m', '<span class="diff_added"> $2</span>', $str);
-	$str = trim($str);
+	$str = rtrim($str);
 	$str = <<<EOD
 $hr
 <ul>
@@ -189,13 +208,15 @@
   <ul>
 EOD;
 	$retval[1] = "\n";
-	$retval[2] = <<<EOD
-  </ul>
+	$retval[2] = "  </ul>\n  <ol>\n";
+	$retval[3] = '';
+	$retval[4] = <<<EOD
+  </ol>
  </li>
 </ul>
 EOD;
 
-	$backups = _backup_file_exists($page) ? get_backup($page) : array();
+	$backups = get_backup($page);
 	if (empty($backups)) {
 		$msg = str_replace('$1', make_pagelink($page), $_msg_nobackup);
 		$retval[1] .= '   <li>' . $msg . '</li>' . "\n";
@@ -209,17 +230,18 @@
 
 	$href = $script . '?cmd=backup&amp;page=' . $r_page . '&amp;age=';
 	$_anchor_from = $_anchor_to   = '';
-	foreach ($backups as $age=>$data) {
+	foreach ($backups as $utime) {
 		if (! PLUGIN_BACKUP_DISABLE_BACKUP_RENDERING) {
-			$_anchor_from = '<a href="' . $href . $age . '">';
+			$_anchor_from = '<a href="' . $href . $utime . '">';
 			$_anchor_to   = '</a>';
 		}
-		$date = format_date($data['time'], TRUE);
-		$retval[1] .= <<<EOD
-   <li>$_anchor_from$age $date$_anchor_to
-     [ <a href="$href$age&amp;action=diff">$_msg_diff</a>
-     | <a href="$href$age&amp;action=nowdiff">$_msg_nowdiff</a>
-     | <a href="$href$age&amp;action=source">$_msg_source</a>
+		$date = format_date((int)$utime, TRUE);
+		$retval[3] .= <<<EOD
+   <li>$_anchor_from$date$_anchor_to
+     [ <a href="$href$utime&amp;action=diff">$_msg_diff</a>
+     | <a href="$href$utime&amp;action=nowdiff">$_msg_nowdiff</a>
+     | <a href="$href$utime&amp;action=source">$_msg_source</a>
+     | <a href="$href$utime&amp;action=delete">削除</a>
      ]
    </li>
 EOD;
@@ -233,12 +255,60 @@
 {
 	global $cantedit;
 
-	$pages = array_diff(get_existpages(BACKUP_DIR, BACKUP_EXT), $cantedit);
+	$pages = array_diff(backup_file_exists(), $cantedit);
 
 	if (empty($pages)) {
 		return '';
 	} else {
 		return page_list($pages, 'backup', $withfilename);
 	}
+}
+
+function plugin_backup_convert($action)
+{
+	global $vars, $_msg_invalidpass;
+
+	$title_backup_convert = 'バックアップ方式の変更';
+	$msg_import = '標準バックアップ形式からインポートする';
+	$msg_export = '標準バックアップ形式にエクスポートする';
+	$msg_adminpass = '管理者パスワード';
+	$msg_complete = '変更終了';
+	$btn_submit = '実行';
+	$body = '';
+	if (isset($vars['pass'])) {
+		if (pkwk_login($vars['pass'])) {
+			if($action == 'import'){
+				backup_import(FALSE);
+			}else{
+				backup_export(FALSE);
+			}
+			return array('msg'  => $title_backup_convert, 'body' => $msg_complete);
+		}
+		$body = '<p><strong>' . $_msg_invalidpass . '</strong></p>' . "\n";
+	}
+
+	if($action == 'import'){
+		$check_import = ' checked="checked"';
+		$check_export = '';
+	}else{
+		$check_import = '';
+		$check_export = ' checked="checked"';
+	}
+	$script = get_script_uri();
+	$body .= <<<EOD
+<form action="$script" method="post">
+ <div>
+  <input type="hidden" name="cmd" value="backup" />
+  <input type="radio" name="action" value="import" id="_p_backup_import"$check_import />
+  <label for="_p_backup_import">$msg_import</label><br />
+  <input type="radio" name="action" value="export" id="_p_backup_export"$check_export />
+  <label for="_p_backup_export">$msg_export</label><br />
+  <label for="_p_backup_adminpass">$msg_adminpass</label>
+  <input type="password" name="pass" size="12" id="_p_backup_adminpass" />
+  <input type="submit" name="ok" value="$btn_submit" />
+ </div>
+</form>
+EOD;
+	return	array('msg'=>$title_backup_convert, 'body'=>$body);
 }
 ?>

従来型データ用のlib/backup.php の差し替えファイル

backup.phpは差分の方が長くなるので…

<?php
/**
 *
 * PukiWiki - Yet another WikiWikiWeb clone.
 *
 * backup.php
 *
 * バックアップを管理する
 *
 * @package org.pukiwiki
 * @access  public
 * @author
 * @create
 * @version $Id: backup.php,v 1.9 2005/04/30 05:21:00 henoheno Exp $
 * Copyright (C)
 *   2002-2005 PukiWiki Developers Team
 *   2001-2002 Originally written by yu-ji
 * License: GPL v2 or (at your option) any later version
 **/

if(extension_loaded('zlib')){
	define('BACKUP_EXT', '.gz');
	if(version_compare(PHP_VERSION, '4.3.0') < 0)
		define('BACKUP_PROTOCOL_WRAPPER', 'zlib:');
	else
		define('BACKUP_PROTOCOL_WRAPPER', 'compress.zlib://');
}else{
	define('BACKUP_EXT', '.txt');
	define('BACKUP_PROTOCOL_WRAPPER', '');
}

/**
 * make_backup
 * バックアップを作成する
 *
 * @access    public
 * @param     String    $page        ページ名
 * @param     Boolean   $delete      TRUE:バックアップを削除する
 *
 * @return    Void
 */

function make_backup($page, $delete = FALSE)
{
	global $cycle, $maxage;
	global $do_backup, $del_backup;

	if (PKWK_READONLY || ! $do_backup) return;

	if (($del_backup && $delete) || $maxage < 1) {
		backup_delete($page);
		return;
	}

	if (! is_page($page)) return;

	$lastmod = _backup_get_filetime($page);
	if ($lastmod == 0 || UTIME - $lastmod > 60 * 60 * $cycle)
	{
		$filename = _backup_get_filename($page);
		$die_msg = 'cannot write file ' . htmlspecialchars($filename) .
			'<br />maybe permission is not writable or filename is too long';
		do{
			if(($lock = @fopen($filename, 'r')) !== FALSE){
				flock($lock, LOCK_EX);
				clearstatcache();
				if(_backup_get_filetime($page) != $lastmod)
					break;

				$fp = @fopen(BACKUP_PROTOCOL_WRAPPER . $filename, 'rb') or
					die_message('cannot read file ' . htmlspecialchars($filename));
				//	.gzのfreadは解凍後のサイズ分読み込むのでループ必須
				for($data = ''; !feof($fp); $data .= fread($fp, 1048576));
				fclose($fp);

				$regex_splitter = '/^' . preg_quote(PKWK_SPLITTER, '/') . ' (\d+)\n/m';
				$data = preg_split($regex_splitter, $data, -1, PREG_SPLIT_DELIM_CAPTURE);
				//	最初のsplitterより前のデータを捨てる(通常は空文字列)
				array_shift($data);
				$count = (count($data) >> 1) + 1;

				// 直後に1件追加するので、(最大件数 - 1)を超える要素を捨てる
				if ($count > $maxage)
					array_splice($data, 0, ($count - $maxage) << 1);
			}else{
				$lock = fopen($filename, 'a+')
					or die_message($die_msg);
				flock($lock, LOCK_EX);
				clearstatcache();
				if(filesize($filename) != 0)
					break;

				$data = array();
			}
			$regex_splitter = '/^(' . preg_quote(PKWK_SPLITTER, '/') . ' \d+)$/';
			//	暫定的に差分から前回の更新日時を得る
			$utime = filemtime(DIFF_DIR . encode($page) . '.txt') - LOCALZONE;
			// Escape 'lines equal to PKWK_SPLITTER', by inserting a space
			$body = PKWK_SPLITTER . ' ' . $utime . "\n";
			$body .= rtrim(implode('', preg_replace($regex_splitter, '$1 ', get_source($page)))) . "\n";

			$fp = fopen(BACKUP_PROTOCOL_WRAPPER . $filename, 'wb')
				or die_message($die_msg);
			while(!empty($data)) {
				fputs($fp, PKWK_SPLITTER . ' ' . array_shift($data) . "\n"); // Splitter format
				fputs($fp, array_shift($data));
			}
			fputs($fp, $body);
			fclose($fp);
			_backup_set_filetime($page, UTIME);
		}while(FALSE);
		flock($lock, LOCK_UN);
		fclose($lock);
	}
}

/**
 * get_backup
 * バックアップを取得する
 * $utime = 0または省略 : 全てのバックアップ作成日時を配列で取得する
 * $utime > 0           : 指定した世代のバックアップデータを取得する
 *
 * @access    public
 * @param     String    $page        ページ名
 * @param     Integer   $utime       バックアップの作成日時
 *
 * @return    String    バックアップ           ($utime > 0)
 *            Array     バックアップ時刻の配列 ($utime == 0)
 */
function get_backup($page, $utime = 0)
{
	static $c_page, $data, $lists;

	if($page != $c_page){
		$c_page = $page;
		$data = $lists = array();
		$regex_splitter = '/^' . preg_quote(PKWK_SPLITTER, '/') . ' (\d+)\n/m';
		$filename = _backup_get_filename($page);

		if(($lock = @fopen($filename, 'r')) === FALSE)
			return $utime == 0 ? array() : '';
		flock($lock, LOCK_SH);
		$fp = fopen(BACKUP_PROTOCOL_WRAPPER . $filename, 'rb') or
			die_message('cannot read file ' . htmlspecialchars($filename));
		for($data = ''; !feof($fp); $data .= fread($fp, 1048576));
		fclose($fp);
		flock($lock, LOCK_UN);
		fclose($lock);

		$data = preg_split($regex_splitter, $data, -1, PREG_SPLIT_DELIM_CAPTURE);
		foreach($data as $key=>$val){
			if($key & 1){
				unset($data[$key]);
				$lists[] = (int)$val;
			}
		}
		array_shift($data);
	}

	if($utime == 0){
		return $lists;
	}
	$key = array_search($utime, $lists);
	return is_int($key) ? $data[$key] : '';
}

/**
 * backup_file_exists
 * バックアップファイルが存在するか
 *
 * @access    public
 * @param     String    $page        ページ名
 *
 * @return    Boolean   TRUE:ある FALSE:ない
 *            Array     バックアップの存在するページの配列(ページ名省略時)
 */
function backup_file_exists($page = NULL)
{
	if(isset($page))
		return file_exists(_backup_get_filename($page));

	return get_existpages(BACKUP_DIR, BACKUP_EXT);
}

/**
 * backup_delete
 * バックアップファイルを削除する
 *
 * @access    public
 * @param     String    $page        ページ名
 * @param     Integer   $utime       バックアップの作成日時
 *
 * @return    Boolean   FALSE:失敗
 */
function backup_delete($page, $utime = NULL)
{
	$filename = _backup_get_filename($page);

	if(!isset($utime))
		return @unlink($filename);

	if(($lock = @fopen($filename, 'r')) === FALSE)
		return FALSE;
	flock($lock, LOCK_EX);
	do{
		$success = FALSE;
		$regex_splitter = '/^' . preg_quote(PKWK_SPLITTER, '/') . ' (\d+)\n/m';
		$filename = _backup_get_filename($page);

		if(($fp = @fopen(BACKUP_PROTOCOL_WRAPPER . $filename, 'rb')) === FALSE)
			break;
		for($data = ''; !feof($fp); $data .= fread($fp, 1048576));
		fclose($fp);

		$data = preg_split($regex_splitter, $data, -1, PREG_SPLIT_DELIM_CAPTURE);
		array_shift($data);
		$lists = array();
		foreach($data as $key=>$val){
			if(($key & 1) == 0) $lists[$key] = (int)$val;
		}

		$key = array_search($utime, $lists);
		if(!is_int($key))
			break;
		array_splice($data, $key, 2);

		$filetime = _backup_get_filetime($page);
		if(($fp = @fopen(BACKUP_PROTOCOL_WRAPPER . $filename, 'wb')) === FALSE)
			break;
		while(!empty($data)) {
			fputs($fp, PKWK_SPLITTER . ' ' . array_shift($data) . "\n"); // Splitter format
			fputs($fp, array_shift($data));
		}
		fclose($fp);
		$success = _backup_set_filetime($page, $filetime);
	}while(FALSE);
	flock($lock, LOCK_UN);
	fclose($lock);
	return $success;
}

/**
 * backup_import
 * バックアップ形式を標準データ形式からインポートする
 *
 * @access    public
 * @param     Boolean   $keep_data   元データを削除しない
 *
 * @return    Void
 */
function backup_import($keep_data)
{
	if(BACKUP_EXT == '.txt')
		die_message('this backup system is standard data format');
	if(ini_get('safe_mode') == '0')
		set_time_limit(0);

	foreach(get_existpages(BACKUP_DIR, '.txt') as $page){
		$src  = BACKUP_DIR . encode($page) . '.txt';
		$dist = BACKUP_DIR . encode($page) . BACKUP_EXT;
		if(file_exists($dist)){
			error_log('PukiWiki Notice: ' . htmlspecialchars($src) . ' is skipped.', 0);
			continue;
		}
		$f_src = @fopen($src, 'rb') or
			die_message('cannot read file ' . htmlspecialchars($src));
		$f_dist = @fopen(BACKUP_PROTOCOL_WRAPPER . $dist, 'wb') or
			die_message('cannot write file ' . htmlspecialchars($dist));
		while(!feof($f_src))
			fputs($f_dist, fread($f_src, 1048576));
		fclose($f_dist);
		fclose($f_src);
		touch($dist, filemtime($src));
		if(!$keep_data && !@unlink($src))
			die_message('cannot remove file ' . htmlspecialchars($src));
	}
}

/**
 * backup_export
 * バックアップ形式を標準データ形式にエクスポートする
 *
 * @access    public
 * @param     Boolean   $keep_data   元データを削除しない
 *
 * @return    Void
 */
function backup_export($keep_data)
{
	if(BACKUP_EXT == '.txt')
		die_message('this backup system is standard data format');
	if(ini_get('safe_mode') == '0')
		set_time_limit(0);
	$regex_splitter = '/^' . preg_quote(PKWK_SPLITTER, '/') . ' (\d+)\n/m';

	foreach(get_existpages(BACKUP_DIR, BACKUP_EXT) as $page){
		$src  = BACKUP_DIR . encode($page) . BACKUP_EXT;
		$dist = BACKUP_DIR . encode($page) . '.txt';
		if(file_exists($dist)){
			error_log('PukiWiki Notice: ' . htmlspecialchars($src) . ' is skipped.', 0);
			continue;
		}
		$f_src = @fopen(BACKUP_PROTOCOL_WRAPPER . $src, 'rb') or
			die_message('cannot read file ' . htmlspecialchars($src));
		$f_dist = @fopen($dist, 'wb') or
			die_message('cannot write file ' . htmlspecialchars($dist));
		$buf = $utime = NULL;
		$time_lists = array();
		while(!feof($f_src)){
			$buf .= fread($f_src, 1048576);
			$data = preg_split($regex_splitter, $buf, -1, PREG_SPLIT_DELIM_CAPTURE);
			if(!feof($f_src)){
				$buf = array_pop($data);
			}else{
				unset($buf);
			}
			if(isset($utime)){
				array_unshift($data, $utime);
			}else if(!empty($data)){	//	最初のsplitterより前のデータを捨てる
				array_shift($data);
			}

			while(!empty($data)){
				$utime = (int)array_shift($data);
				while(in_array($utime, $time_lists))
					$utime++;		//	同一タイプスタンプがある場合、1秒を加算
				if(!empty($data)){
					$time_lists[] = $utime;
					fputs($f_dist, PKWK_SPLITTER . ' ' . $utime . "\n");
					fputs($f_dist, array_shift($data));
				}
			}
		}
		fclose($f_dist);
		fclose($f_src);
		touch($dist, filemtime($src));
		if(!$keep_data && !@unlink($src))
			die_message('cannot remove file ' . htmlspecialchars($src));
	}
}

////////////////	private function follows	////////////////

/**
 * _backup_get_filename
 * バックアップファイル名を取得する
 *
 * @access    private
 * @param     String    $page        ページ名
 *
 * @return    String    バックアップのファイル名
 */
function _backup_get_filename($page)
{
	return BACKUP_DIR . encode($page) . BACKUP_EXT;
}

/**
 * _backup_get_filetime
 * バックアップファイルの更新時刻を得る
 *
 * @access    private
 * @param     String    $page        ページ名
 *
 * @return    Integer   ファイルの更新時刻(GMT)
 */
function _backup_get_filetime($page)
{
	if (!backup_file_exists($page) ||
		($time = filemtime(_backup_get_filename($page))) === FALSE)
			return 0;
	return $time - LOCALZONE;
}

/**
 * _backup_set_filetime
 * バックアップファイルの更新時刻をセットする
 *
 * @access    private
 * @param     String    $page        ページ名
 * @param     Integer   $utime       ファイルの更新時刻(GMT)
 *
 * @return    Boolean   FALSE:失敗
 */
function _backup_set_filetime($page, $utime)
{
	return touch(_backup_get_filename($page), $utime + LOCALZONE);
}
?>

ディレクトリ管理仕様のlib/backup.php の差し替えファイル

ディレクトリで管理するタイプのbackup.php

<?php
/**
 *
 * PukiWiki - Yet another WikiWikiWeb clone.
 *
 * backup.php
 *
 * バックアップをディレクトリで管理する  by Cue
 *
 * @package org.pukiwiki
 * @access  public
 * @author
 * @create
 * @version $Id: backup.php,v 1.9 2005/04/30 05:21:00 henoheno Exp $
 * Copyright (C)
 *   2002-2005 PukiWiki Developers Team
 *   2001-2002 Originally written by yu-ji
 * License: GPL v2 or (at your option) any later version
 **/

define('BACKUP_DIR_PERMISSION',  0777);

if(extension_loaded('zlib')){
	define('BACKUP_EXT', '.gz');
	if(version_compare(PHP_VERSION, '4.3.0') < 0)
		define('BACKUP_PROTOCOL_WRAPPER', 'zlib:');
	else
		define('BACKUP_PROTOCOL_WRAPPER', 'compress.zlib://');
}else{
	define('BACKUP_EXT', '.txt');
	define('BACKUP_PROTOCOL_WRAPPER', '');
}

/**
 * make_backup
 * バックアップを作成する
 *
 * @access    public
 * @param     String    $page        ページ名
 *            Boolean   $delete      TRUE:バックアップを削除する
 *
 * @return    Void
 */
function make_backup($page, $delete = FALSE)
{
	global $cycle, $maxage;
	global $do_backup, $del_backup;

	if (PKWK_READONLY || ! $do_backup) return;

	if (($del_backup && $delete) || $maxage < 1) {
		backup_delete($page);
		return;
	}

	if (! is_page($page)) return;

	$backup = &new backup($page);
	$lastmod = $backup->get_lastmod();
	if(UTIME > $lastmod + 60 * 60 * $cycle){
		if($lastmod == 0 && !$backup->create_dir())
			die_message('cannot create directory ' . htmlspecialchars($backup->dirname) .
				'<br />maybe permission is not writable or filename is too long');

		for($backup_lists = $backup->lists();
			count($backup_lists) >= $maxage;
			$backup->delete(array_shift($backup_lists))
		);

		//	暫定的に差分から前回の更新日時を得る
		$utime = filemtime(DIFF_DIR . encode($page) . '.txt') - LOCALZONE;
		$backup->append(implode('', get_source($page)), $utime);
	}
}

/**
 * get_backup
 * バックアップを取得する
 * $utime = 0または省略 : 全てのバックアップ作成時刻を配列で取得する
 * $utime > 0           : 指定した時刻のバックアップデータを取得する
 *
 * @access    public
 * @param     String    $page        ページ名
 *            Integer   $utime       バックアップの作成時刻
 *
 * @return    String    バックアップ           ($utime != 0)
 *            Array     バックアップ時刻の配列 ($utime == 0)
 */
function get_backup($page, $utime = 0)
{
	$backup = &new backup($page);
	if($utime == 0){
		return array_keys($backup->lists());
	}
	$lines = $backup->read($utime);
	return $lines !== FALSE ? implode('', $lines) : '';
}

/**
 * backup_file_exists
 * バックアップが存在するか
 *
 * @access    public
 * @param     String    $page        ページ名
 *
 * @return    Boolean   TRUE:ある FALSE:ない
 *            Array     バックアップの存在するページ名の配列(ページ名省略時)
 */
function backup_file_exists($page = NULL)
{
	if(isset($page)){
		$backup = &new backup($page);
		return file_exists($backup->dirname);
	}
	return get_existpages(BACKUP_DIR, '');
}

/**
 * backup_delete
 * バックアップを削除する
 *
 * @access    public
 * @param     String    $page        ページ名
 *            Integer   $utime       バックアップ作成時刻(省略時は全て)
 *
 * @return    Boolean   FALSE:失敗
 */
function backup_delete($page, $utime = NULL)
{
	$backup = &new backup($page);
	return $backup->delete($utime);
}

/**
 * backup_import
 * バックアップ形式を標準データ形式からインポートする
 *
 * @access    public
 * @param     Boolean   $keep_data   元データを削除しない
 *
 * @return    Void
 */
function backup_import($keep_data)
{
	if(ini_get('safe_mode') == '0')
		set_time_limit(0);
	$regex_splitter = '/^' . preg_quote(PKWK_SPLITTER, '/') . ' (\d+)\n/m';

	foreach(get_existpages(BACKUP_DIR, '.txt') as $page){
		$import_file = BACKUP_DIR . encode($page) . '.txt';
		$backup = &new backup($page);
		if(file_exists($backup->dirname)){
			error_log('PukiWiki Notice: ' . htmlspecialchars($import_file) . ' is skipped.', 0);
			continue;
		}
		if(!$backup->create_dir())
			die_message('cannot create directory ' . htmlspecialchars($backup->dirname) .
				'<br />maybe permission is not writable or filename is too long');
		$fp = @fopen($import_file, 'rb') or
			die_message('cannot read file ' . htmlspecialchars($import_file));
		$buf = $utime = NULL;
		while(!feof($fp)){
			$buf .= fread($fp, 1048576);
			$data = preg_split($regex_splitter, $buf, -1, PREG_SPLIT_DELIM_CAPTURE);
			if(!feof($fp)){
				$buf = array_pop($data);
			}else{
				unset($buf);
			}
			if(isset($utime)){
				array_unshift($data, $utime);
			}else if(!empty($data)){	//	最初のsplitterより前のデータを捨てる(通常は空文字列)
				array_shift($data);
			}

			while(!empty($data)){
				$utime = (int)array_shift($data);
				while(file_exists($backup->filename($utime)))
					$utime++;		//	同一タイプスタンプがある場合、1秒を加算
				if(!empty($data))
					$backup->append(rtrim(array_shift($data))."\n", $utime);
			}
		}
		fclose($fp);
		$lists = $backup->lists();
		if(empty($lists))
			die_message('empty data file found ' . htmlspecialchars($import_file));
		$backup->set_lastmod(filemtime($import_file) - LOCALZONE);
		if(!$keep_data && !@unlink($import_file))
			die_message('cannot remove file ' . htmlspecialchars($import_file));
	}
}

/**
 * backup_export
 * バックアップ形式を標準データ形式にエクスポートする
 *
 * @access    public
 * @param     Boolean   $keep_data   元データを削除しない
 *
 * @return    Void
 */
function backup_export($keep_data)
{
	if(ini_get('safe_mode') == '0')
		set_time_limit(0);
	$regex_splitter = '/^(' . preg_quote(PKWK_SPLITTER, '/') . ' \d+)$/';

	foreach(get_existpages(BACKUP_DIR, '') as $page){
		$export_file = BACKUP_DIR . encode($page) . '.txt';
		if(file_exists($export_file)){
			error_log('PukiWiki Notice: ' . htmlspecialchars($export_file) . ' is skipped.', 0);
			continue;
		}
		$fp = @fopen($export_file, 'wb') or
			die_message('cannot write file ' . htmlspecialchars($export_file));
		$backup = &new backup($page);
		foreach(array_keys($backup->lists()) as $utime){
			fputs($fp, PKWK_SPLITTER . ' ' . $utime . "\n");
			$lines = $backup->read($utime) or
				die_message('cannot read file ' . htmlspecialchars($backup->filename($utime)));
			fputs($fp, implode('', preg_replace($regex_splitter, '$1 ', $lines)));
		}
		fclose($fp);
		touch($export_file, $backup->get_lastmod() + LOCALZONE);
		if(!$keep_data && !$backup->delete())
			die_message('cannot remove directory ' . htmlspecialchars($backup->dirname));
	}
}

////////////////    private function follows    ////////////////

class backup
{
	var $page;
	var $dirname;

	function backup($page){
		$this->page = $page;
		$this->dirname = BACKUP_DIR . encode($this->page) . '/';
	}

	function filename($utime){
		return $this->dirname . $utime . BACKUP_EXT;
	}

	function get_lastmod(){
		$time = @filemtime($this->dirname . 'index.html');
		return $time !== FALSE ? $time - LOCALZONE : 0;
	}

	function set_lastmod($utime){
		return touch($this->dirname . 'index.html', $utime + LOCALZONE);
	}

	function create_dir(){
		mkdir($this->dirname);
		chmod($this->dirname, BACKUP_DIR_PERMISSION);
		if(($fp = @fopen($this->dirname . 'index.html', 'wb')) === FALSE)
			return FALSE;
		fputs($fp, "backup files are placed here.\n");
		fclose($fp);
		return TRUE;
	}

	function lists(){
		$lists = $matches = array();
		if(($dp = @opendir($this->dirname)) !== FALSE){
			while($filename = readdir($dp)){
				if(preg_match('/^(\d+)'.preg_quote(BACKUP_EXT, '/').'$/', $filename, $matches))
					$lists[(int)$matches[1]] = $this->dirname . $filename;
			}
			closedir($dp);
			ksort($lists);
		}
		return $lists;
	}

	function delete($utime = NULL){
		if(isset($utime))
			return @unlink($this->filename($utime));

		$success = TRUE;
		foreach($this->lists() as $filename)
			$success &= @unlink($filename);
		if($success)
			$success &= @unlink($this->dirname . 'index.html');
		return $success ? @rmdir($this->dirname) : FALSE;
	}

	function read($utime){
		return @file(BACKUP_PROTOCOL_WRAPPER . $this->filename($utime));
	}

	function append($body, $utime){
		$filename = $this->filename($utime);
		$die_msg = 'cannot write file ' . htmlspecialchars($filename) .
			'<br />maybe permission is not writable or filename is too long';

		$lock = @fopen($filename, 'a+') or
			die_message($die_msg);
		flock($lock, LOCK_EX);
		clearstatcache();
		if(filesize($filename) === 0){
			$fp = @fopen(BACKUP_PROTOCOL_WRAPPER . $filename, 'wb') or
				die_message($die_msg);
			fputs($fp, $body);
			fclose($fp);
			$this->set_lastmod(UTIME);
		}
		flock($lock, LOCK_UN);
		fclose($lock);
	}
}
?>

*1 どこかのアップローダーに置いた方が良いかなぁ…

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2010-03-08 (月) 13:08:43
Site admin: PukiWiki Development Team

PukiWiki 1.5.2+ © 2001-2019 PukiWiki Development Team. Powered by PHP 5.6.40-0+deb8u8. HTML convert time: 0.223 sec.

OSDN