2012年2月26日日曜日

Zend Framework + WURFLでPC、携帯、スマホのUA判定


いつまで経ってもUA判定面倒だぜ。。

って、やっぱりみんな思ってる。
WURFL(Wireless Universal Resource FiLe)というモバイル判定を行うデータベースが整備されています。

ZendのUserAgent判定にWURFLを組み込む方法。

参考)
http://framework.zend.com/manual/ja/zend.http.user-agent.html
http://framework.zend.com/manual/en/zend.http.user-agent-features-wurfl.html


0. 環境

PHP 5.3.10
Zend 1.11.10
wurfl-php-1.3.1


1. WURFLのダウンロードと配置

WURFL PHPをダウンロード。
http://sourceforge.net/projects/wurfl/files/
http://sourceforge.net/projects/wurfl/files/WURFL%20PHP/

任意のディレクトリに展開してOKだけど、Zendのドキュメントに書いてある通り、application/libraryに展開します。

library
 └wurfl-php-1.3.1
  ├docs
  ├examples
  ├tests
  ├tools
  ├WURFL
  ├COPYING
  └README


2. キャッシュディレクトリの設定

WURFLデータベースのストレージとしてFile、Memcache、Memory、Mysqlの4つが使えるっぽいです。

以下、簡単そうなFileストレージを採用します。
WURFLのXMLファイルをリクエスト毎にパースしていたら大変なのでFileストレージは最初のアクセス時にキャッシュを作っておきます。

/wurfl-php-1.3.1/WURFL/Storage/File.php
を見てみると、デフォルトのキャッシュパスが/var/tmpになっています。

これをZendのドキュメントに倣って
APPLICATION_ROOT/data/wurfl/cache
とします。

mkdir -p data/wurfl/cache
chmod -R o+rwX data/wurfl/cache


3. データファイルコピー

wurfl-php-1.3.1/tests/resources/
配下のWURFLデータベースを先ほど作ったdataディレクトリにコピーします。

cp library/wurfl-php-1.3.1/tests/resources/wurfl-2.0.27.zip data/wurfl/
cp library/wurfl-php-1.3.1/tests/resources/web_browsers_patch.xml data/wurfl/


4. 設定

Zend先生は
application/config/wurfl-config.php
を作って、
application.ini
からwurfl-config.phpをロードしなさい、と言われていますが、キャッシュディレクトリの設定が上手くいきません。

参考)
<?php
$resourcesDir            = dirname(__FILE__) . '/../../data/wurfl/';
 
// $wurfl['main-file']      = $resourcesDir  . 'wurfl-latest.zip';
$wurfl['main-file']      = $resourcesDir  . 'wurfl-2.0.27.zip';
$wurfl['patches']        = array($resourcesDir . 'web_browsers_patch.xml');
 
$persistence['provider'] = 'file';
$persistence['dir']      = $resourcesDir . '/cache/';
 
$cache['provider']       = null;
 
$configuration['wurfl']       = $wurfl;
$configuration['persistence'] = $persistence;
$configuration['cache']       = $cache;

この辺りのドキュメントのwurflapi.wurfl_config_array.*パラメタをwurfl-config.phpで設定しておきなさい、ということのようですが、
wurflapi.wurfl_config_array.persistence.dir
が効かないっぽいですし、
wurflapi.wurfl_config_array.cache.*
なんてドキュメントには無い。

よく分かんないですが、wurfl-config.phpを作らずにapplication.iniから設定します。

; application/configs/application.ini
; WURFL API
;
resources.useragent.wurflapi.wurfl_lib_dir = APPLICATION_PATH "/../library/wurfl-php-1.3.1/WURFL/"
resources.useragent.wurflapi.wurfl_api_version = "1.1"
resources.useragent.wurflapi.wurfl_config_array.wurfl.main-file = APPLICATION_PATH "/../data/wurfl/wurfl-2.0.27.zip"
resources.useragent.wurflapi.wurfl_config_array.wurfl.patches = APPLICATION_PATH "/../data/wurfl/web_browsers_patch.xml"
resources.useragent.wurflapi.wurfl_config_array.persistence.provider = "file"
resources.useragent.wurflapi.wurfl_config_array.persistence.dir.dir = APPLICATION_PATH "/../data/wurfl/cache"

; resources.useragent.wurflapi.wurfl_config_array.persistence.dir = FOO
; キャッシュディレクトリは恐らくこう↑設定するのが正しいのだと思いますが、これだと動作しません。
; /wurfl-php-1.3.1/WURFL/Storage/File.phpのコンストラクタにdirパラメタが渡ってきません。
; こう↓すれば渡ってきます。
; resources.useragent.wurflapi.wurfl_config_array.persistence.dir.dir = FOO
; Zendの不具合??仕様??。


5. トラブルシュート1

これで設定はお終いでパスが正しく設定されていれば動作する、とのことですが、上手く行きません。

キャッシュディレクトリとして作成した
data/wurfl/cache
がWritableじゃないよ、と言われます。

chmod -R o+rwX data/wurfl/cache
してるのにな、と/wurfl-php-1.3.1/WURFL/Storage/File.phpを見てみると、

if(!is_writeable(dirname($this->root))){
    throw new WURFL_Storage_Exception("The file cache directory is not writeable: ".$this->root);
}

となっており、一つ上のdata/wurflディレクトリがWritableでないとエラーが出ます。

if(!is_writeable($this-&gt;root)){
に修正しても良さそうに見えますが、data/wurflのパーミッションを777にして対応しました。


6. トラブルシュート2

Fatal error: Class 'XMLReader' not found

となった場合は

$ yum install php-xml
$ service httpd restart
# それでもダメならコンパイルオプションの問題かも。


7. 実行

適当なコントローラで

$bootstrap = $this->getInvokeArg('bootstrap');
$userAgent = $bootstrap->getResource('useragent');
$device = $userAgent->getDevice();
var_dump($device);

してみると色々取れてて面白い。

// モバイル、PC、タブレット判定
$device->getFeature('is_mobile');
$device->getFeature('is_desktop');
$device->getFeature('is_tablet');
// モバイル、スマホ判定
$device->getFeature('device_os')
// SSLサポート
$device->httpsSupport();
// 最大画像サイズ(?)
$device->getMaxImageWidth();
$device->getMaxImageHeight();
// 画面解像度(?)
$device->getPhysicalScreenWidth();
// Flashサポート
$device->hasFlashSupport()
// audioサポート
if ($userAgent->hasFeature('mp3') && $userAgent->getFeature('mp3')) {
    // embed HTML5 audio tag...
}


8. 付録

エミュレータを使ったスマホ判定実験結果一覧。

端末名 OS device_os device_os_token brand_name
DC GALAXY NEXUS(SC-04D) Android 4.0 Android Android 4.0.1 Android
SB iPad iOS5 iPhone OS iPhone OS Apple
SB iPhone4 iOS5 iPhone OS iPhone OS Apple
DC HT1100 Windows Mobile 6 Windows Mobile OS Windows CE Generic
DC BlackBerry Bold 9700 BlackBerry OS5.0 RIM OS NULL RIM
DC NM850iG Symbian OS 8.0 NULL NULL DoCoMo

Symbianだけガラケー判定しそうだけど、基本的にはdevice_osの有り無しで見てしまっていいんではないだろうか。。

2012年2月16日木曜日

CentOS6.2 KVM環境構築

CentOS5.4で運用してきましたがそろそろ6.xにアップデート。
Xenで管理してきた仮想化環境を6系からCentOS標準になったKVMに移行。

以下、その記録。

http://www.oss-d.net/virt/kvm
こちらを参考に。

■環境

$ cat /etc/redhat-release
CentOS release 6.2 (Final)

$ cat /proc/version
Linux version 2.6.32-220.4.2.el6.x86_64 (mockbuild@c6b18n3.bsys.dev.centos.org) (gcc version 4.4.6 20110731 (Red Hat 4.4.6-3) (GCC) ) #1 SMP Tue Feb 14 04:00:16 GMT 2012

■KVMインストール

$ yum groupinfo kvm
Loaded plugins: fastestmirror, refresh-packagekit
Setting up Group Process
Loading mirror speeds from cached hostfile
 * base: rsync.atworks.co.jp
 * extras: rsync.atworks.co.jp
 * updates: rsync.atworks.co.jp
Warning: Group kvm does not exist.

おっと、いきなりダメじゃん。

ここまで辿り着くまでにRAID組んだりX入れたりVNC入れたり色々用意してたんですが、CentOS6.2で管理されてるパッケージがこれまでと地味に色々変わってる。

$ yum groupinstall Virtualization "Virtualization Client" "Virtualization Platform" "Virtualization Tools"

が正解。
参考 http://unix.stackexchange.com/questions/18802/how-to-install-kvm-on-centos-6

後は基本的に書かれてある通り。

■ブリッジ設定

$ cp /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/sysconfig/network-scripts/ifcfg-br0
$ vi /etc/sysconfig/network-scripts/ifcfg-br0

DEVICE="br0"
BOOTPROTO="static"
BROADCAST="192.168.12.255"
DNS1="192.168.12.1"
GATEWAY="192.168.12.1"
#HWADDR="00:22:19:0B:6F:F0"
IPADDR="192.168.12.51"
NETMASK="255.255.255.0"
#NM_CONTROLLED="yes"
ONBOOT="yes"
TYPE="Bridge"

$ vi /etc/sysconfig/network-scripts/ifcfg-eth0

DEVICE="eth0"
#BOOTPROTO="static"
#BROADCAST="192.168.12.255"
#DNS1="192.168.12.1"
#GATEWAY="192.168.12.1"
HWADDR="00:22:19:0B:6F:F0"
#IPADDR="192.168.12.51"
#NETMASK="255.255.255.0"
#NM_CONTROLLED="yes"
ONBOOT="yes"
TYPE="Ethernet"
BRIDGE="br0"

$ service network restart

$ vi /etc/sysctl.conf
# 恐らく変更の必要ない

ここまででブリッジを見るように設定できた。
ただ、rebootしたりするとWANが見えなくなる。
何でかよく分からないけどservice network restartすると見える。

■仮想マシンマネージャー起動

X上で
$ virt-manager

Error polling connection 'qemu:///system': internal error Cannot find suitable
emulator for x86_64

となれば、恐らくBIOSのVirtualizationが無効になっている。
BIOS設定からVirtualizationをenableに変更すればOKだった。

■ゲストOSインストール(失敗)

virt-managerからGUIで仮想マシンをインストールしようとすると、ネットワーク越しのインストールが出来ない。
URIを指定してインストールを試みると
The location must be the root directory of an install tree.
とかって言われる。

検索してみてもバグ?とか何とか、よく分からない。
しょうがないのでローカルメディアからインストーラを起動してネットワークインストール。

■仮想マシンインストール(続き)

CDROMにネットワークインストールのメディアを焼いてそこからインストールを試みた。
途中までは良かったけれど、インストール中にUSB接続キーボードのアンダースコアが表示できない。

keymapとか色々あるんだろう。

ググってみてもVmwareの場合はいくつか出てくるけど、KVMはよく分からない。

仕方がないのでUSのキーボードレイアウトでインストール実行。
「ほ」の辺りのキーをShift押しながらでアンダースコア。

無理やり感漂うけれど、まぁ、インストールはできた。

インストール後日本語レイアウトに変更。

$ vi /etc/sysconfig/keyboard

KEYTABLE="us"
MODEL="pc105+inet"
LAYOUT="us"
KEYBOARDTYPE="pc"

↓

KEYTABLE="jp106"
MODEL="jp106"
LAYOUT="jp"
KEYBOARDTYPE="pc"

アンダースコアも大丈夫。

・ゲストOS環境

$ cat /etc/redhat-release
CentOS release 6.2 (Final)

$ cat /proc/version
Linux version 2.6.32-220.el6.x86_64 (mockbuild@c6b18n3.bsys.dev.centos.org) (gcc version 4.4.6 20110731 (Red Hat 4.4.6-3) (GCC) ) #1 SMP Tue Dec 6 19:48:22 GMT 2011

2012年2月1日水曜日

Android, Viewにstyleを動的に設定する方法

AndroidアプリのButtonやViewに動的にstyleを設定したい。
例えばActivityからnew Button()したようなオブジェクトに共通で使っているstyleを設定したい。

かなり探したけど全然見つからなかった。
唯一ここに発見。
ソースコード内で生成したViewインスタンスにStyleを適用

はじめは書いてある内容が理解できなかったので、備忘録として以下メモ。
#リンク先と同じこと書いてるだけ。

・アウトライン

Viewのコンストラクタを見てると
View(Context context)
View(Context context, AttributeSet attrs)
View(Context context, AttributeSet attrs, int defStyle)
こんな風に定義されている。

Contextはクラスのオブジェクトを指定。この辺りを参照。
AttributeSetはXMLで指定した場合のプロパティが渡ってくる。
style指定は3つ目のdefStyle。

なので、Activityから生成したViewオブジェクトにstyleを指定した場合は、3つ目のパラメタにR.foo.barのリソースIDを渡せばよい。
っていうのは比較的すぐ分かるんですが、具体的にどうやるのかは一苦労。

以下。

・styleの指定

R.styleというのがあるので、こいつを指定すればいいんだろうと格闘してましたが、どうやら違ってR.attrでないとダメな模様。
このstyleリソースIDを直接指定できれば楽なんだけどなー、というのが最初のリンク先で触れられていた内容。

・R.attrについて

R.attrのドキュメント

ドキュメントを見る限り、R.attrはwidthやcheckedのようなAndroidのXMLエレメントのアトリビュートを定義してるものらしい。
R.attrの中にはdialogLayoutやbuttonStyleのようなレイアウト周りのリソースIDを指定していると思われるものも存在しますが、本来styleとは直接は関係ない(はず)。
なのでR.attrの指定からしかstyleを適用できない、というのは勘違いかAndroidのバグだと思う。

でも他にやり方が分からないので以下進めます。
#詳しい方、教えて。。

・R.attrを指定する

XMLはres/values/style.xmlで指定する。
#ここじゃないとダメなのかはよく分からない。

1. 適用させるstyleを作成
<!-- <item name="android:layout_*">の要素はR.attrでスタイル適用出来ないっぽい -->
<style name="MyButtonStyle" parent="android:style/Widget.Button">
    <item name="android:layout_width">match_parent</item>
    <item name="android:layout_height">wrap_content</item>
    <item name="android:layout_marginBottom">2dp</item>
    <item name="android:textColor">@color/foo</item>
    <item name="android:textSize">16dp</item>
    <item name="android:background">@drawable/bar</item>
    <item name="android:textStyle">bold</item>
</style>

作ったstyleをR.attrで参照可能なように設定。

2. テーマの適用

theme属性としてstyleを設定するので、先にテーマをアプリケーションに適用させておく。
AndroidManifest.xml
  <application
      android:icon="@drawable/ic_launcher"
      android:label="@string/app_name"
+     android:theme="@style/MyApplicationStyle" >


3. テーマの設定

<style name="MyApplicationStyle" parent="android:Theme">
</style>


4. styleのテーマへの組み込み

+ <attr name="myButtonStyle" format="integer|reference" />
  <style name="MyApplicationStyle" parent="android:Theme">
+      <item name="myButtonStyle">@style/MyButtonStyle</item>
  </style>

これでR.attr.myButtonStyleが参照可能になった。

5. Activityでの適用

Button btn = new Button(this, null, R.attr.myButtonStyle);

でstyle定義済みのViewオブジェクトが生成可能。
後は如何様にも。

btn.setText("foo");
LinearLayout layout = new LinearLayout(this);
layout.addView(
    btn,
    new LinearLayout.LayoutParams(
        LinearLayout.LayoutParams.WRAP_CONTENT,
        LinearLayout.LayoutParams.WRAP_CONTENT));
setContentView(layout);

とか。