まだ重たいCMSをお使いですか?
毎秒 723リクエスト を捌く超高速CMS「adiary

2017/04/22(土)Web Pushの実装まとめ(Chrome/Firefox/Android共通)

2017/02/26(日)最近のDebianに、ソースから Perl 5.8.9 のインストール

adiaryの動作テスト用に Perl 5.8 が欲しかったので、ソースからインストールした時のメモ。

手順

cd /usr/local/src
wget http://www.cpan.org/src/perl-5.8.9.tar.gz
./configure.gnu -Dprefix=/usr/local/perl5.8 -Dusethreads -Duselargefiles \
-Dccflags=-DDEBIAN -D_FORTIFY_SOURCE=2-fstack-protector-strong \
-Dldflags=-Wl,-z,relro -Dlddlflags=-shared -Dcccdlflags=-fPIC \
-Darchname=x86_64-linux-gnu -Dusesitecustomize -Duse64bitint \
-Dpager=/usr/bin/sensible-pager \
-Uafs -Ud_csh -Ud_ualarm -Uusesfio -Uusenm -Ui_libutil -Uversiononly \
-DDEBUGGING=-g -Doptimize=-O2 -Duseshrplib -des

このままコンパイルすると crypt() が使えないので config.sh を手動修正。

d_crypt='define'

この後で make する。

make
make test
make install

参考にしたサイト

2015/05/20(水)Perl DBI と UTF8フラグ と 文字化け問題

Perl 5.20にしたら、DBIが何やら不可解な動作をするようになりました。

問題の原因

どうやら UTF8 を扱う機能が増えたみたいです。DBIの接続時のパラメーターにこんなものが増えています。

pg_enable_utf8 => 1 , # 結果をUTF8フラグ付きにする(PostgreSQL)

mysql_enable_utf8 => 1 , # 結果をUTF8フラグ付きにする(MySQL)

PerlのDBIモジュールで自動的にUTF8フラグを付ける

この影響で、PostgreSQL運用のシステムを Perl 5.14 から 5.20 にアップグレードしたところ文字化け発生。また厄介なことをしてくれたものです(汗)

保存されいてる文字コードがおかしい

普通に使っているのに「Wide character in print at」の警告がなぜか出まくります。日本語UTF-8として保存した文字列が文字化けしまくるのですが、そのデータだけ単独に取り出して表示しても化けない。

色々調べてみると以下のことがわかりました。

  • 新DBD は(utf8フラグのついていない)文字列を DB に保存するとき「ASCII文字列に特殊な文字が混ざってる」と解釈してエスケープ処理を行う。
  • この文字列は、通常の utf8 文字列ではないので、データベースの中身を直接覗くと、保存した文字列ではなく特殊なエスケープ文字列として表示される。

PostgreSQLではこんな感じです。

  • 文字列「あいう」を保存。バイナリ列で「E3 83 86 E3 82 B9 E3 83 88」
  • psqlコンソールで表示される文字列「a\u0083\u0086a\u00821a\u0083\u0088」
  • DBI経由で取り出し、utf8フラグを取り除いた文字列「C3 A3 C2 83 C2 86 C3 A3 C2 82 C2 B9 C3 A3 C2 83 C2 88」

同じUTF8文字列でありながら、違う内部表現になっています。当然、元の「あいう」と文字列比較を行うと異なると判定されます。

MySQLではこんな感じです。

  • mysqlコンソールで出力される文字列「C3 A3 C2 81 E2 80 9A C3 A3 C2 81 E2 80 9E C3 A3 C2 81 E2 80 A0」
  • DBI経由で取り出した文字列は utf8フラグ がついていない元の文字列。

問題を整理すると2つに集約できます。

  • DBにきちんとしたUTF8文字列として保存されない。
  • DBに一回保存することで、内部表現の異なる UTF-8 文字列が生成されてしまう(PostgreSQL)。

後者も問題と言えば問題ですが、前者はとても気持ち悪い問題です。

解決策

utf8フラグはうまく使えば「Perlによきに計らってもらい」文字コードの問題が楽になるものですが、utf8文字列をutf8として「そのまま扱いたい」時には問題が多すぎる仕組みです。入出力の都度にencode、decodeが発生し処理を重くする原因にもなります。

またutf8フラグありとフラグなしのUTF8文字列が混在すると、それらを連結したときに文字化けする危険があります。

そうなるとutf8フラグなしで扱いたいのですが、そうするためにはどうしたらいいのでしょうか。

DBD::Pg編

  • utf8フラグを付けた文字列を保存すれば、そのままutf8文字列としてDBに保存される。
  • utf8フラグのない文字列は、必要に応じて特殊エンコードされてDBに保存される。
  • 「pg_enable_utf8」を設定しないとき。
    • 非ASCII文字列や、特殊エンコードされた文字列は utf8 フラグが付いて取り出される。
  • 「pg_enable_utf8 => 1」のとき。
    • 「pg_enable_utf8」を設定しないときと同様。
  • 「pg_enable_utf8 => 0」のとき。
    • いかなる場合も utf8 フラグは付けなくなる。
    • 特殊エンコードされた文字列は文字化けする。

取り出す時用に「pg_enable_utf8 => 0」を設定して、保存時や「検索時」にutf8フラグを付けるのがよさそうです。

DBD::MySQL

  • DB保存時はutf8フラグの有無はすべて無視する(動作は変化しない)。
  • 「mysql_enable_utf8」を設定しないとき。
    • いかなる場合もutf8フラグは付かない。
    • 非ASCII文字列は特殊エンコードされたDBに保存される。
    • 特殊エンコード文字列は、通常の utf8 文字列で取り出される。
    • DBに格納されている通常の utf8 文字列は、エンコード失敗の「?」に置換されDBから取り出される。
  • 「mysql_enable_utf8 => 0」のとき。
    • 「mysql_enable_utf8」を設定しないときと同様。
  • 「mysql_enable_utf8 => 1」のとき。
    • utf8文字列は、通常の utf8 文字列としてDBに保存される。
    • 非ASCII文字列は utf8 フラグが付いて取り出される。
    • 特殊エンコードされた文字列は文字化けする。

DBD::Pgとは実装がまるで違う事がわかります。

ドキュメントを読んだところ「SET NAMES utf8;」というSQL文を発行することで、同じ効果を得て、かつutf8フラグに影響がでないことがわかりました。ただこの状態では、特殊エンコードされた文字列を読み込むときに文字化けするので注意が必要です。

比べると

DBD::Pgの実装のほうが謎かもしれない。DBD::Pgはトランザクション処理がおかしいという素敵な実績があるからなあ……。「pg_enable_utf8 => 0」のとき保存データのutf8フラグも無視しないと矛盾するのはすぐに分かりそうなもんなんだけど。

DBIの仕様

Perl supports two kinds of strings: Unicode (utf8 internally) and non-Unicode (defaults to iso-8859-1 if forced to assume an encoding). Drivers should accept both kinds of strings and, if required, convert them to the character set of the database being used. Similarly, when fetching from the database character data that isn't iso-8859-1 the driver should convert it into utf8.

http://search.cpan.org/~timb/DBI-1.633/DBI.pm

Perl は Unicode(内部表現utf8)と 非Unicode(iso-8859-1)の2つの文字コードをサポートします。DBDドライバはこの2つの文字コードを受け付けるべきで、必要に応じて database で使用可能な文字列に変換する必要があります。

同様に、iso-8859-1ではない文字列は database から取り出す段階でutf8に変換すべきです。

まとめ

  • 今どき文字コード問題、しかも UTF8 で悩むとは思わなかった。
  • 1byte文字圏の UTF8 実装は相変わらずクソ。

2015/05/14(木)スマホでドラッグ&ドロップのエミュレーション

jQuery UIdynatree を使用していて、ドラッグアンドドロップ操作が必須であるにも関わらずスマホでは何もできないので、汎用的な実装をjQuery pluginで実現しました。

タッチ操作でDnDをエミュレーションする

タッチパネル系イベントは独特らしく、短くタップしたときのみ mousedown や click 等のマウスイベントが発生してくれますが、長く触っているとマウスイベントは発生しないようです。

ですので、以下のように実装しました。

  • touchstart で mousedown を発火。
  • touchmove で mousemove を発火し、mouseleave と mouseenter をエミュレーション。
  • touchend で mouseup を発火。

mouseover, mouseoutも実装はできますが無視しました。jQuery UIが問題なく動く程度には実装しているつもりです。

エミュレーションがonの状態で短くタップすると、mousedown/mouseupイベントが2重に発生する可能性がありますが、解決策がないので保留です。

ソース

修正BSDライセンスとします。jQuery pluginですので適当に読み込ませて次のように使ってください。

$(dom).dndEmulation();

以下のソースは最新でない可能性があります。最新版は、adiaryのサイトからGitHub経由で「js/adiary.js」を参照して該当部のみ抜粋してください。該当部のみ抜粋する限り修正BSDライセンスで扱って構いません。

var TouchDnDTime  = 700;
$.fn.extend({
//////////////////////////////////////////////////////////////////////////////
// Copyright (C)2015 nabe@abk, New BSD License.
//////////////////////////////////////////////////////////////////////////////
dndEmulation: function(){
	var self = this[0];
	if (!self) return;

	// mouseイベント作成
	function make_mouse_event(name, evt, touch) {
		var e = $.Event(name);
		e.altKey   = evt.altKey;
		e.metaKey  = evt.metaKey;
		e.ctrlKey  = evt.ctrlKey;
		e.shiftKey = evt.shiftKey;
		e.clientX = touch.clientX;
		e.clientY = touch.clientY;
		e.screenX = touch.screenX;
		e.screenY = touch.screenY;
		e.pageX   = touch.pageX;
		e.pageY   = touch.pageY;
		e.which   = 1;
		return e;
	}
	// 自分自身を含めた親要素をすべて取得
	function get_par_elements(dom) {
		var ary  = [];
		while(dom) {
			ary.push( dom );
			if (dom == self) break;
			dom = dom.parentNode;
		}
		return ary;
	}

	// クロージャ変数
	var prev;
	var flag;

	// mousedownエミュレーション
	this.bind('touchstart', function(_evt){
		var evt = _evt.originalEvent;
		prev = evt.target;
		var e = make_mouse_event('mousedown', evt, evt.touches[0]);
		$( prev ).trigger(e);
		
		// ある程度時間が経過しないときは処理を無効化する。
		flag = false;
		setTimeout(function(){
			flag=true;
		}, TouchDnDTime)
	});

	// mouseupエミュレーション
	this.bind('touchend', function(_evt){
		var evt = _evt.originalEvent;
		var e = make_mouse_event('mouseup', evt, evt.changedTouches[0]);
		$( evt.target ).trigger(e);
	});

	// ドラッグエミュレーション
	this.bind('touchmove', function(_evt){
		var evt = _evt.originalEvent;

		// 一定時間立たなければ、処理を開始しない
		if (!flag) return;

		var touch = evt.changedTouches[0];
		var dom   = document.elementFromPoint(touch.clientX, touch.clientY);
		var enter = get_par_elements(dom);

		// マウス移動イベント
		var e = make_mouse_event('mousemove', evt, touch);
		$(enter).trigger(e);

		// 要素移動がなければこれで終了
		evt.preventDefault();
		if (dom == prev) return;

		// 要素移動があれば leave と enter イベント生成
		var leave = get_par_elements(prev);

		// 重複要素を除去
		while(leave.length && enter.length
		   && leave[leave.length -1] == enter[enter.length -1]) {
			leave.pop();
			enter.pop();
		}

		// イベント発火
		var e_leave = make_mouse_event('mouseleave', evt, touch);
		var e_enter = make_mouse_event('mouseenter', evt, touch);
		$(leave).trigger( e_leave );
		$(leave).trigger( e_enter );

		// 新しい要素を保存
		prev=dom;
	});
}
//////////////////////////////////////////////////////////////////////////////
});

2015/05/13(水)Lightbox2 にスワイプ対応パッチを充てた

充てたというかパッチしました。作りました。jQueryのみです。

画像をめくるときにタッチパネルのスワイプ操作でも反応するようになっています。特にアニメーションはしません。

jQueryのバグなのか「touchstart」「touchmove」イベントを全く拾わないので、「window.addEventListener」に逃げました。もしもっとスマートな解決方法を見つけたら教えてください。

これを使用しているadiaryもよろしくお願いします

その他の変更点

#lightbox-min-width {
	display:		none;
	width:			300px;
}

と設定すると、横幅が小さすぎる画像を表示するとき、指定したピクセルまでアスペクト比を保持して拡大して表示します。

極端に縦長の画像の場合はアスペクト比を保持できて、画面に収まる範囲内で拡大されます。

OK キャンセル 確認 その他