フォト
2009年11月
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30          
無料ブログはココログ

パソコン・インターネット

2009年10月25日 (日)

いよいよjqGrid使ってみる

 jqGridは、JavaScriptライブラリであるjQueryのプラグインで、リッチな感じのテーブルだ。見た目、結構かっこ良さ気なんだよね。Excelっぽくて。

 こいつを使って、DBを検索した結果を表示してみようと思った。

■参照したサイト

  • jQuery本家
    JavaScriptライブラリjQueryや、jQueryベースのテーマフレームワークjQuery UIを、ダウンロードできたりマニュアル読めたり。ただし全部英語。
  • jQuery日本語リファレンス
    jQueryの日本語リファレンスのサイト。本家の英語の翻訳に疲れたときに利用。お世話になってます。
  • jqGrid配布元
    jQueryプラグインのjqGridのダウンロードとか最新版のリリース情報とか。英語サイト。
  • jqGrid Wiki
    jqGridの使い方とかリファレンスとか。インストールや最初の一歩的簡単な使い方から書かれてる。英語サイト。
  • jqGridのデモ
    jqGridのデモページ。各機能毎にデモを見れる。

■ファイルの配置

 最初jQueryを使い始めた頃、どのファイルをどう配置していいのかよくわかんなかったんだが、使ってみるとあんまりそこは気にしなくてもいいという気がしてくる。ちゃんとjQueryを使うページからリンクするように正しくパスが書かれていれさえすれば。

 ただダウンロードしてきたファイル名にバージョン番号が含まれていることがあって、バージョンアップしてファイル名が変わってしまったときの手間を考えてバージョン番号を含まない名前にリネームする気遣いくらいはした。

 一応今回自分が配置した様子はこんな感じ。あくまでも一例ってことで。

+ インストールディレクトリ
index.php (メイン画面 自作)
search.php (Ajax返答用 自作)
+ js
   jquery.js (jquery-1.3.2.min.jsをリネーム)
   jquery-ui.js (jquery-ui-1.7.2.custom.min.jsをリネーム)
   jquery.jqgrid.js (jquery.jqGrid.min.jsをリネーム)
   + i18n (jqGridに同梱されているディレクトリ)
+ css
   ui.jqgrid.css (jqGridに同梱)
   + ui-lightness (jQuery UIに同梱 テーマによって名前変わる?)
     jquery-ui.css (jquery-ui-1.7.2.custom.cssをリネーム)

■メイン画面のヘッダ(1)

 JavascriptファイルやらCSSファイルやらを指定しておかなくては。

 jqGridはjQueryのプラグインなので、当然jqGridのファイルと一緒にjQueryのファイルも必要となる。さらにjqGridの見た目はjQuery UIに制御されているようなので、それも指定しておく必要がある。

 その辺、実はjqGrid Wikiの中に「My First Grid」というページがあって、例が示されていた。

<link rel="stylesheet" href="./css/ui-lightness/jquery-ui.css"
    type="text/css" />
<link rel="stylesheet" href="./css/ui.jqgrid.css" type="text/css" />

<script type="text/javascript" src="./js/jquery.js"></script>
<script type="text/javascript" src="./js/i18n/grid.locale-jp.js">
</script>
<script type="text/javascript" src="./js/jquery.jqgrid.js">
</script>

 もう一つ、jqGridの本体についての記述が必要になる。次のような感じで書いてみた。

<script type="text/javascript">
$(document).ready(function(){
//jqGrid初期化
$("#list").jqGrid({
  url:'search.php',
  datatype: 'json',
  mtype: 'POST',
  colNames:[
    'id','クリスタル','アイテム名', 'ランク',
    'スキル','限界','素材','説明'
  ],
  colModel :[
   {name:'item_id', index:'item_id', width:1, hidden:true},
   {name:'crystal_name', index:'crystal_name',
                              width:90, align:'center'},
   {name:'item_name', index:'item_name', width:80, align:'center'},
   {name:'rank_name', index:'rank_name', width:80, align:'center'},
   {name:'skill_name', index:'skill_name', width:80, align:'center'},
   {name:'skill_cap', index:'skill_cap', width:80, align:'center'},
   {name:'material_item', index:'material_item',
                              width:80, align:'left'},
   {name:'item_text', index:'item_text', width:80, align:'left'}
  ],
  rowNum:10,
  sortname: 'skill_cap',
  sortorder: 'asc',
  caption: '検索結果'
});
});
</script>

 まず基本的に、jQueryのコードはページが読み込まれたとき(正確にはDocumentがReadyになったとき?)に実行するものらしい。なので、HTMLファイルのヘッダ部に、

<script type="text/javascript">
$(document).ready(function(){
  // ここに任意のコード
});
</script>

 と書くのがお決まりらしい。今回のも実際に中にはjqGridの記述をしてある。

 さてそのjqGrid。まず基本は、

$("#list").jqGrid({ /* ここにいろいろ初期値を書く */ });

 と書く。冒頭の「#list」は、「id="list"と書かれているタグ」を意味している。こう書くと、id="list"と書かれているタグにjqGridの表が描かれる。

 中身はというと、jqGridに関するオプションがいろいろ書いてある。

 urlは相手のurl。特に今回作ろうとしているもので言うと、このurlに対して画面上で指定された検索条件を送信して、このurlに示す相手が検索結果を送り返してきてくれる。

 datatypeはurlで指定した相手がどういう形式でデータを返すのか。今回はjsonを指定してある。もちろん相手が最後にjson形式で送信するようにコーディングされていなければならない。

 mtypeはurlで指定した相手がどのメソッドでデータを受け取るか。言い換えると、jqGrid側がデータをどう送るか。postかgetかで言う。

 colNamesとcolModelは、jqGridで表示する表の列の定義だな。colNamesが実際に表示される列名になる。colModelの方は俺もあまりよく理解していない。特にindexとnameがどう違うのか・・・。実際サーバ側から検索結果データを送りつけるときもこの名前を使っているわけでもないし・・・。

■ボディ部

 jqGridに関するボディ部の記述は至って簡単。

<table id="list"></table>

 この1行だけでいい。tableタグだけ書いて、中身はいらない。jqGridが勝手に動的に書いてくれるから。

 それと、検索条件を指定するためのformを書いておかなければ。

<form>
名前 <input size="60" type="text" id="item_name" />
<input type="button" value="&nbsp;検索&nbsp;" id="btnSearch" />
<input type="reset" value="クリア" id="btnClear" /><br />
スキル <select id="skill_name"><?php skill_option(); ?></select> 
ランク <select id="rank_name"><?php rank_option(); ?></select> 
クリスタル <select id="crystal_name">
<?php crystal_option(); ?></select>
<label for="cbxText">
<input type="checkbox" id="cbxIncludeMaterial" />素材も含む
</label>
</form>

 このformで直接データをpostやgetで送るわけではないので、formタグにはactionとかmethodとかは書いてないし、type="submit"なボタンも用意していない。

 ちなみにselectタグで囲まれてるphpの部分は、optionタグを書くもの。中身はDBから読んできてるが、実際のところはそうそう変化する値ではないので直書きでも良かったな。

■検索ボタンで検索したい

 ここまでで、とりあえず画面には検索条件を入力するためのフォームとjqGridのテーブルは表示される。でも、検索に関するコードは一切書いてないのでどこをどうしてもウンともスンとも言わない。

 ここから、その検索のコードを書き加える。

 jqGrid自体はヘッダ部で書いたオプションに従って画面がロードされた時に一回だけサーバ側と通信するが、後はそれっきりだったりする。実はjqGridにはpostDataというオプションがあって、指定しておくとサーバ側と通信するときにpostDataで指定したデータを送ってくれるんだが、何にもしなければそのデータも画面がロードされた時に一回だけしか送ってくれない。

 でもやりたいことは、画面がロードされた後にページを使う人がform内の検索条件を指定して検索ボタンをクリックしたときに検索をさせたいってことだ。

 それをするために(だよなきっと)、jqGridにはsetGridParam()というメソッドと、trigger("reloadGrid")というメソッドが装備されている。

 検索ボタンをクリックしたときの動作として、その時点の検索条件をsetGridParam()を使ってpostDataに突っ込んでおいて、即座にtrigger("reloadGrid")してあげれば、望んでいる動作をしてくれる。

■メイン画面のヘッダ(2)

 先ほど書いたヘッダ部のJavaScriptに少し書き加える。

<script type="text/javascript">
$(document).ready(function(){
//jqGrid初期化
$("#list").jqGrid({
  // 前述と同じなので省略
});
//検索ボタンのOnClickイベントハンドラ(追加部分)
$("#btnSearch").click(function(){
  $("#list").setGridParam({
   postData : {
    item_name        : $("#item_name").val(),
    skill_id         : $("#skill_name option:selected").val(),
    rank_id          : $("#rank_name option:selected").val(),
    crystal_id       : $("#crystal_name option:selected").val(),
    include_material :
     ($("#cbxIncludeMaterial").val()=='on')?true:false
   }
  });
  $("#list").trigger("reloadGrid");
});
});
</script>

 こう書いておくと、検索ボタンをクリックしたときに最初のjqGridのオプションで書いたurlで指定されるサーバ側のファイルへpostDataの値が送られる。

■サーバ側スクリプト

 jqGridのオプションurlで指定されたファイルの方はというと、postデータが渡されるのでそれを元にDB検索して結果をjson形式で表示するスクリプトを組むことになる。

 俺が作ったphpのスクリプトは、こう。

//ほしいpostデータが無ければ即終了
if(!array_key_exists('item_name',$_POST)) return;

//postデータの保存
$item_name = $_POST['item_name'];
$skill_id = $_POST['skill_id'];
$rank_id = $_POST['rank_id'];
$crystal_id = $_POST['crystal_id'];
$include_material = $_POST['include_material'];

//DB検索
$pdo = new db_pdo();
$result =
$pdo->SearchRecipe($item_name,$skill_id,$rank_id,$crystal_id);

//検索結果返却用配列作成
$rtn = array();
for($i=0;$i<count($result);$i++){
$rec = $result[$i];
$rtnrec['id'] = $rec['item_id'];
$rtnrec['cell'] =
  array($rec['item_id'],$rec['crystal_name'],$rec['item_name'],
        $rec['rank_name'],$rec['skill_name'],
        $rec['skillcap'.($rec['skill_id']+1)],
        $rec['item1_name'].'<br />'.$rec['item2_name'].'<br />'.
        $rec['item3_name'].'<br />'.$rec['item4_name'].'<br />'.
        $rec['item5_name'].'<br />'.$rec['item6_name'].'<br />'.
        $rec['item7_name'].'<br />'.$rec['item8_name'],
        $rec['item_text']);
  $rtnrec = convert_utf8($rtnrec);
  $rtn[] = $rtnrec;
}
$responce->page = 1;
$responce->total = 1;
$responce->records = count($rtnrec);
$responce->rows = $rtn;

echo json_encode($responce);

 json_encode()する対象の配列$responceの作り方は、jqGridのサイトに掲載されていたものをほぼそのままパクった。正直理解し切れていない。ただ['cell']で示される要素の中に、実際表示するデータをぱらぱらと並べている。

 検索ボタンがクリックされたときには検索条件がpostされてくるが、画面ロード時の最初の一回はそのデータが無いはずなので、その考慮もしておいた。

 あと、上記のDB検索時のdb_pdoクラスは自作のもの。抜粋は以下。

public function SearchRecipe($item_name,$skill_id,
                      $rank_id,$crystal_id)
{
//item_nameのwhere句
$item_name = preg_replace('/^\*|\*$/','%',trim($item_name));
if(strlen($item_name)==0) $item_name = '%';
$item_name = mb_convert_encoding($item_name,'UTF-8','AUTO');
//skill_idのwhere句
if($skill_id >= 0)
$where_skill_id = 'recipe.skill_id = '.$skill_id.' and ';
else $wherre_skill_id = '';
//rank_idのwhere句
if($rank_id >= 0)
$where_rank_id = 'recipe.rank_id = '.$rank_id.' and ';
else $wherre_rank_id = '';
//crystal_idのwhere句
if($crystal_id >= 0)
$where_crystal_id = 'recipe.crystal_id = '.$crystal_id.' and ';
else $where_crystal_id = '';
//SQL文生成
$sql = <<< SQLEND
select
recipe.item_id, crystal.crystal_name, item.item_name,
rank.rank_name, skill.skill_name, recipe.skill_id,
recipe.skillcap1, recipe.skillcap2, recipe.skillcap3,
recipe.skillcap4, recipe.skillcap5, recipe.skillcap6,
recipe.skillcap7, recipe.skillcap8, recipe.skillcap9,
item1.item_name as item1_name, item2.item_name as item2_name,
item3.item_name as item3_name, item4.item_name as item4_name,
item5.item_name as item5_name, item6.item_name as item6_name,
item7.item_name as item7_name, item8.item_name as item8_name,
item.item_text
from
  crystal, item, rank, recipe, skill,
  item item1, item item2, item item3, item item4,
  item item5, item item6, item item7, item item8
where
  recipe.item_id = item.item_id and
  recipe.material_item_id1 = item1.item_id and
  recipe.material_item_id2 = item2.item_id and
  recipe.material_item_id3 = item3.item_id and
  recipe.material_item_id4 = item4.item_id and
  recipe.material_item_id5 = item5.item_id and
  recipe.material_item_id6 = item6.item_id and
  recipe.material_item_id7 = item7.item_id and
  recipe.material_item_id8 = item8.item_id and
  recipe.skill_id = skill.skill_id and
  recipe.rank_id = rank.rank_id and
  recipe.crystal_id = crystal.crystal_id and
  {$where_skill_id}
  {$where_rank_id}
  {$where_crystal_id}
  item.item_name like '{$item_name}'
SQLEND;
return $this->execute_sql($sql);
}

public function execute_sql($sql)
{/* 汎用SQL実行・結果はfetchAll()
*/
$sth = $this->PDO->prepare($sql);
$sth->execute();
$rtn = $sth->fetchAll(PDO::FETCH_ASSOC);
return $rtn;
}

 これで検索してみた結果は、こんな感じ。

200910241

 ふぅ~・・・なげぇ(ーoー)y゜゜  しかも俺が書いたSQL文に間違いがあるらしく、検索結果が予定より少ない・・・この後もうちっと書き換え必要だな。素材を含むオプション実装してないし。

2009年10月22日 (木)

表の結合 その2

 jqGridに入る前に、またSQLの壁。勉強が足りませんな>俺

■お題

 一つのテーブルにある値と同じ値を持つ別のレコード上のいろんな項目を持ってくるには、表の結合ってのでできるのはわかった。

 例えば、

tableA
 item_id
 item_value

tableB
 item_id
 item_name

 なんつー2つのテーブルがあって、item_nameとitem_valueを表示したいと思ったら、

select tableB.item_name,tableA.item_value
 from tableA,tableB
 where
  tableA.item_id = tableB.item_id

 とやればできる。

 で、今回悩んだのが、一つのテーブルにある複数の項目がいずれも別の一つのテーブルの同じ項目と同じ意味を持っている場合。

 以前作ったテーブルで説明すると、

テーブル名:recipe
item_id
material_item_id1
material_item_id2
material_item_id3
material_item_id4
material_item_id5
material_item_id6
material_item_id7
material_item_id8
(列は抜粋)

テーブル名:item
item_id
item_name
(列は抜粋)

 こんなテーブルがあって、recipeテーブルで示した各項目はいずれもitemテーブルのitem_idと同じ意味を持つ項目。しかも、recipeテーブルの各列にはほぼ違う値が入る。

 この状態で、recipeテーブルの各idに合致するitem_nameを9つ並べたい。

■トライアンドエラー

 ここでまたクエリーデザイナー様にご登場願う。

 最初に考えたのが、これ(念のため書いとくと、これはマチガイ)。

200910211

 recipeテーブルのitem_idもmaterial_item_id1~8も、itemテーブルのitem_idと同じ意味なのだから、それをらを全部itemテーブルのitem_idにひも付け。かつ、表示したいのはitem_nameだからitemテーブルのitem_nameにチェック。

 そうしてできあがったSQL文が、これ(これもマチガイ)。

SELECT
item.item_name
FROM
recipe INNER JOIN
item ON recipe.item_id = item.item_id AND
recipe.material_item_id1 = item.item_id AND
recipe.material_item_id2 = item.item_id AND
recipe.material_item_id3 = item.item_id AND
recipe.material_item_id4 = item.item_id AND
recipe.material_item_id5 = item.item_id AND
recipe.material_item_id6 = item.item_id AND
recipe.material_item_id7 = item.item_id AND
recipe.material_item_id8 = item.item_id

 実行してみると、

mysql> SELECT
-> item.item_name
-> FROM
-> recipe INNER JOIN
-> item ON recipe.item_id = item.item_id AND
-> recipe.material_item_id1 = item.item_id AND
-> recipe.material_item_id2 = item.item_id AND
-> recipe.material_item_id3 = item.item_id AND
-> recipe.material_item_id4 = item.item_id AND
-> recipe.material_item_id5 = item.item_id AND
-> recipe.material_item_id6 = item.item_id AND
-> recipe.material_item_id7 = item.item_id AND
-> recipe.material_item_id8 = item.item_id;
Empty set (0.00 sec)

mysql>

 検索できないorz

 名前を9個並べたいのにselectの後にはitem_name1個だけだし。明らかにやりたいこととは違うことには気付ける。

 じゃぁどうするか・・・

 ややしばらくいろいろクエリデザイナをいじってたら、itemテーブルを複数並べることができることに気がついた。

 あぁ、これか。itemテーブルをたくさん用意すればいいんだ。

■たどり着いた答え

 一個のitemテーブルだと、どうがんばっても一個のitem_nameしか無いのだろう。なので、ひも付けしたい分だけitemテーブルを並べてしまえ。

200910212

 そうしてできあがったSQL文がこれ。

SELECT
item.item_name,item1.item_name,
item2.item_name,item3.item_name,
item4.item_name,item5.item_name,
item6.item_name,item7.item_name,
item8.item_name
FROM
recipe INNER JOIN
item ON recipe.item_id = item.item_id INNER JOIN
item item1 ON recipe.material_item_id1 = item1.item_id INNER JOIN
item item2 ON recipe.material_item_id2 = item2.item_id INNER JOIN
item item3 ON recipe.material_item_id3 = item3.item_id INNER JOIN
item item4 ON recipe.material_item_id4 = item4.item_id INNER JOIN
item item5 ON recipe.material_item_id5 = item5.item_id INNER JOIN
item item6 ON recipe.material_item_id6 = item6.item_id INNER JOIN
item item7 ON recipe.material_item_id7 = item7.item_id INNER JOIN
item item8 ON recipe.material_item_id8 = item8.item_id

 inner joinがまだよくわかってないので、where句に結合条件を書く書き方で直すと、こう。結果はたくさんはいらないので、最後にrecipe.item_id<10をつけておく。

SELECT
  item.item_name, item1.item_name,
  item2.item_name,item3.item_name,
  item4.item_name,item5.item_name,
  item6.item_name,item7.item_name,
  item8.item_name
 FROM
  recipe , item , 
  item item1 , item item2 , item item3 , item item4 , 
  item item5 , item item6 , item item7 , item item8  
 where
  recipe.item_id = item.item_id and
  recipe.material_item_id1 = item1.item_id and
  recipe.material_item_id2 = item2.item_id and
  recipe.material_item_id3 = item3.item_id and
  recipe.material_item_id4 = item4.item_id and
  recipe.material_item_id5 = item5.item_id and
  recipe.material_item_id6 = item6.item_id and
  recipe.material_item_id7 = item7.item_id and
  recipe.material_item_id8 = item8.item_id and
  recipe.item_id < 10

 で、結果はこれ。

item_name item_name_1 item_name_2 item_name_3 item_name_4 item_name_5 item_name_6 item_name_7 item_name_8
ブロンズベッド ブロンズインゴット ブロンズインゴット ブロンズインゴット ブロンズインゴット 草糸 木綿布 木綿布 サルタ綿花
オークベッド ホワイトオーク材 ホワイトオーク材 ホワイトオーク材 ホワイトオーク材 亜麻糸 亜麻布 亜麻布 亜麻布
ノーブルベッド マホガニー材 ローズウッド材 ローズウッド材 ローズウッド材 ゴールドインゴット 金糸 ビロード 絹布
粗末な寝台 ラワン材 ラワン材 ホリー材 ホリー材 草糸 草布 草布 草布
マホガニーベッド マホガニー材 マホガニー材 マホガニー材 マホガニー材 毛糸 毛織物 毛織物 毛織物
ロイヤルベッド マホガニー材 エボニー材 神代木 ゴールドインゴット ルビー 金糸 絹布 ダマスク織物

 すげぇな。ちゃんとできるもんだね、SQLで。

2009年10月18日 (日)

13日の金曜b(ぉ

 今回の記事は、これから先の話をするための前準備。

 「Ajaxでサーバとクライアント間でデータをやりとりするときは、データをJSON形式にしておくと何かと便利。」と会社の人に言われた。

 確かに使っていると、クライアント側でサーバから受け取るデータをJSON形式と指定するjQueryのプラグインがあったり、サーバ側にしてもphpにjson_encode()という関数があって、ハッシュ配列をJSON形式に変換してくれる関数があったり。

 で、うちでもそのjson_encode()を使おうとすると

「そんな関数ねぇーよ (Call to undefined function json_encode()) 」

 というようなエラーを出す。

 ネットで調べてみたら、この関数はphp5.2から標準で使える関数らしい。うちのCentOS 5.3に入っているphpは5.1.x。peclっていうのを使えば5.1でもインストールできるらしいので早速試す。

 参考にしたのはこちら。

http://se-suganuma.blogspot.com/2008/07/phpjsondecode-jsonencode.html

 ちなみに、これを試すにはphp-develとphp-pearが必要らしいが、無いなら無いでエラー出すだろう、おそらく。

[takeblizzard@localhost local]$ sudo pecl install json
パスワード:
WARNING: channel "pear.php.net" has updated its protocols, use "channel-update pear.php.net" to update
downloading json-1.2.1.tgz ...
Starting to download json-1.2.1.tgz (17,780 bytes)
......done: 17,780 bytes
11 source files, building
running: phpize

(途中大きく省略)

Build process completed successfully
Installing '/var/tmp/pear-build-root/install-json-1.2.1//usr/lib/php/modules/json.so'
install ok: channel://pear.php.net/json-1.2.1
[takeblizzard@localhost local]$

 なんかワラワラとメッセージ出てきたけど、うまくインストールできたみたいだ。

 後は、「/etc/php.d/json.ini」というファイルを作って、中身に「extension=json.so」と1行だけ書いて保存、httpdをリスタート。

 これでjson_encode()が使えたことを確認した。


 前準備ができたところで、今は以前記事に書いたDBの中身を検索するページを作成中。

20091018

 DBからデータを引っ張ってきてドロップダウンに突っ込むところまでは完成。

 この後、この下にテーブルを配置して検索結果を表示してみようと画策中。テーブルに使うのはjqGridの予定。jQueryのプラグインだ。ここでJSON形式の話が絡んでくる。

 次の記事はたぶんその辺のことを書けるはず。

2009年10月12日 (月)

SQL - 表の結合のお勉強

■今回のお題

 例えばこんなテーブルがあったとする。ちなみに列は抜粋だが他の列も似たような感じ。

mysql> select recipe_id,item_id,skill_id,rank_id,crystal_id
-> from recipe where recipe_id < 5;

+-----------+---------+----------+---------+------------+
| recipe_id | item_id | skill_id | rank_id | crystal_id |
+-----------+---------+----------+---------+------------+
|         1 |    4405 |        8 |       3 |          0 |
|         2 |    4466 |        8 |       5 |          0 |
|         3 |   17399 |        3 |       2 |          3 |
|         4 |     605 |        2 |       0 |          0 |
+-----------+---------+----------+---------+------------+
4 rows in set (0.00 sec)

 どこの列も数字ばっかり。

 でも実はこの数字には意味がある。その意味は別のテーブルに定義されている。例えばcrystal_idは、

mysql> select * from crystal;
+------------+--------------+
| crystal_id | crystal_name |
+------------+--------------+
|          0 | 炎           |
|          1 | 氷           |
|          2 | 風           |
|          3 | 土           |
|          4 | 雷           |
|          5 | 水           |
|          6 | 光           |
|          7 | 闇           |
+------------+--------------+
8 rows in set (0.00 sec)

 ってな感じで。

 できれば1回のSQL文実行で、「recipe_idの1は炎」っていう風に出したい。それにはどうしたらいいのか、というのが今回のお題。

■ツール頼みでSQL文作成

 正直SQL初心者でろくに勉強もしていないので、あんまり難しいSQL文を自分で作り出すことができない。

 ところが便利なもので、俺がよく利用しているSQLクライアントツール「黒猫SQL Studio」でクエリーデザイナーという機能があり、ビジュアル的にSQL文を作成できるらしいので、そいつを頼ることにした。

 ちなみにこのツールはWindows用。・・・本当はMac上で使いやすいのがあればそっちを使いたいところだがイマイチまだ見つけ出せてないという、、、

 ツールを起動して、メニューの【クエリー(Q)】→【クエリーデザイナ(W)】をクリックすると、【ビジュアルクエリーデザイナ】というウインドウが表示される。

200910121

 ここで使用するテーブルをダブルクリックして表示させる。使うのはrecipe、item、skill、rank、crystal。

200910122

 次に、同じ意味を持つ項目同士をドラッグ&ドロップして繋げる。ここでは、recipeのitem_idとitemのitem_id、recipeのskill_idとskillのskill_id、recipeのrank_idとrankのrank_id、recipeのcrystal_idとcrystalのcrystal_idが同じ意味を持つ項目になる。

200910123

 そして、実際に表示させたいのはidの数字ではなくて日本語の名前の方なので、itemのitem_name、skillのskill_name、rankのrank_name、crystalのcrystal_nameにチェックを入れる。

200910124

 ここで【OK】ボタンをクリックすると、今デザイナで操作していた内容がSQL文になってツール本体の方へ反映される。

200910125

 できあがったSQL文は次のようなもんだった。

SELECT
item.item_name,
skill.skill_name,
rank.rank_name,
crystal.crystal_name
FROM
recipe INNER JOIN
item ON recipe.item_id = item.item_id INNER JOIN
skill ON recipe.skill_id = skill.skill_id INNER JOIN
rank ON recipe.rank_id = rank.rank_id INNER JOIN
crystal ON recipe.crystal_id = crystal.crystal_id

 俺にとって見慣れない単語が出てきている・・・INNER JOINってなんですk

 またしてもネット漁り。

http://www.pursue.ne.jp/jouhousyo/SQLDoc/select21.html

 へぇー、こういうのを表の結合っていうのね・・・って説明を見ると

SELECT文において複数の表を結合するときはWHERE句で結合条件を指定するほかに(以下省略)

って書いてある。

■あーそういうことだったのk

 そういや今仕事で引き継ぎしてるツールの中にこんなSQLの記述あったのを思い出した。そっち風の書き方をすると、こう。

select
 item.item_name,crystal.crystal_name,
 skill.skill_name,rank.rank_name
from
 recipe,item,crystal,skill,rank
where
 recipe.item_id = item.item_id and
 recipe.crystal_id = crystal.crystal_id and
 recipe.skill_id = skill.skill_id and
 recipe.rank_id = rank.rank_id

 こっちはinner joinを使わない。表示したい項目を並べるときはテーブル名.項目名として、fromには使用するテーブルを全部並べておいて、whereにはどのテーブルのどの項目が同じ意味なのかを記述する。

■SQL文実行結果

 実際にrecipe_idが1のやつだけ出してみる。

mysql> select
    ->  item.item_name,crystal.crystal_name,
    ->  skill.skill_name,rank.rank_name
    -> from
    ->  recipe,item,crystal,skill,rank
    -> where
    ->  recipe.item_id = item.item_id and
    ->  recipe.crystal_id = crystal.crystal_id and
    ->  recipe.skill_id = skill.skill_id and
    ->  recipe.rank_id = rank.rank_id and
    ->  recipe.recipe_id = 1
    -> ;
+-----------+--------------+------------+-----------+
| item_name | crystal_name | skill_name | rank_name |
+-----------+--------------+------------+-----------+
| おにぎり  | 炎           | 調理       | 下級職人  |
+-----------+--------------+------------+-----------+
1 row in set (0.04 sec)

 なぁーんだこれが表の結合だったんだ今頃理解したorz

 そんなわけで、inner joinについてはまた次に出会ったときに勉強することにした。

2009年10月 8日 (木)

MySQL ~ load dataf ileからphpによる出力まで

 少し仕事が忙しくなって少し間が開いたが、前回の続き。ここで早速言い訳を。

 この手の作業、やはりまだWindows慣れしているようで・・・今回からクライアント側はBoot Campを立ち上げてWindows上で、サーバ側はLinuxを使って作業している。

 何だろうなぁ。まだMacを使いこなせてない証拠だな(´Д`;)

 サーバ側の環境的にはApache2.2.3+MySQL5.0.77+PHP5.1.6といった感じ。

■NULLの扱い

 Warningの原因として最初は直感だったんだが、これか?と思うところはあった。それはNULLの扱い。

 読み込むデータの中には所々「NULL」と書かれている項目が存在している。その項目は当然NULL値が入ることを期待しているのだろう。

 考えてみれば、他のデータの部分はそのまま読み込むのに対して、NULLと書いてあるところだけMySQL側で適当に変換して読み込んでくれることを期待するわけだ。ちゃんとそういう機能があるってことを確認しておかないとおっかない。

 ネットを漁っていたら、MySQLのヘルプでNULLに関する記述を見つけることが出来た。

引用元 : http://dev.mysql.com/doc/refman/4.1/ja/problems-with-null.html

LOAD DATA INFILE でデータを読み取ると、空のカラムは '' で更新されます。カラムに NULL 値が必要な場合は、テキストファイルで \N を使用してください。

 だそうだ。早速対処してみる。

 前回記事に書いた文字コードの話を抜きにして、まずはテキストファイルの中身を「NULL」→「¥N」で一括置換だけした。データベースの文字コードもデフォルトのまま。

mysql> load data local infile
-> 'item.full.txt'
-> into table item;
Query OK, 9212 rows affected, 26 warnings (0.77 sec)
Records: 9212 Deleted: 0 Skipped: 0 Warnings: 23

 ずいぶんワーニングの数が減った。俺の勘も少しは役に立つようだ。

■文字コード

 もう一度文字セットの設定を確認してみる。読み込もうとしているデータファイルの文字コードはShift-JISだ。

 まず、文字セットを特に指定せずにデータベースを作成してデータを取り込む。

mysql> drop database takeblizzard;
Query OK, 1 row affected (0.00 sec)

mysql> create database takeblizzard;
Query OK, 1 row affected (0.00 sec)

mysql> use takeblizzard
Database changed
mysql> create table item(
-> item_id int(10) not null,
-> item_name varchar(32) not null,
-> item_text text,
-> is_rare enum('0', '1'),
-> is_ex enum('0', '1'),
-> stack_amount int(10),
-> item_name_english varchar(32),
-> inserted_date datetime,
-> updated_date datetime,
-> item_text_english text
-> );
Query OK, 0 rows affected (0.00 sec)

mysql> load data local infile
-> 'item.full.txt'
-> into table item;
Query OK, 9212 rows affected, 26 warnings (0.10 sec)
Records: 9212 Deleted: 0 Skipped: 0 Warnings: 23

mysql>

 全部のレコードを読み込んだがWarningがいくつか出ている。

 で、これをデータベースの文字コードをあらかじめShift-JISに設定して読み込むとどうなるか。

mysql> drop database takeblizzard;
Query OK, 1 row affected (0.00 sec)

mysql> create database takeblizzard default character set sjis;
Query OK, 1 row affected (0.00 sec)

mysql> use takeblizzard
Database changed
mysql> create table item(
-> item_id int(10) not null,
-> item_name varchar(32) not null,
-> item_text text,
-> is_rare enum('0', '1'),
-> is_ex enum('0', '1'),
-> stack_amount int(10),
-> item_name_english varchar(32),
-> inserted_date datetime,
-> updated_date datetime,
-> item_text_english text
-> );
Query OK, 0 rows affected (0.00 sec)

mysql> load data local infile
-> 'item.full.txt'
-> into table item;
Query OK, 9212 rows affected (0.12 sec)
Records: 9212 Deleted: 0 Skipped: 0 Warnings: 0

mysql>

 エラーもワーニングも出ずに、正常に読み込んでくれた。なるほど、ちゃんとデフォルト文字コードの設定が効いている訳ね。

■PHPで読んでブラウザ上に表示

 DB上にはShift-JISのデータを用意できている。これをPHPで読み込んで表示するだけだ。

 phpのソースは以下のようにして表示してみた。

//■間違いあり■
//PDOオブジェクト生成
$dbh = new PDO('mysql:host=localhost;dbname=takeblizzard',
  'takeblizzard','●●●●●');

//SQL文の準備
$sql = <<< ENDSQL
select item_name
from item
where item_id < 100
ENDSQL;

//SQL文の実行
$sth = $dbh->prepare($sql);
$sth->execute();

//結果をフェッチして表示
while($data = $sth->fetch(PDO::FETCH_ASSOC)){
  echo mb_convert_encoding($data['item_name'],
      "EUC-JP","SJIS").'<br />';
}

 表示結果。

????
???????
??????
???????
???????
????????
????????
???
??????
?????
??????
??
????????
??
????
?????
?????
(以下省略)

 はいはいはいすみませんごめんなさい俺がわるーござんした。

 またネット上を漁ることに。次のような記事を見つけた。

http://wiki.bit-hive.com/tomizoo/pg/MySQL%20%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9%B4%D8%CF%A2

 記事によると、日本語を扱う際はサーバ側とクライアント側でそれぞれ文字コードを設定しておかなければならないらしい。サーバ側はcreate databaseするときにセットしてあるのでいいとして、クライアント側もやっとかなきゃならんとは・・・

 で、この記事の「クライアントの文字コード設定」のところ。要は前回の記事でやってた手順のうちの「SET CHARACTER SET SJIS」をやればいいってことだ。。

 phpのソースを手直し。事前に上記SQL文を投げるようにする。

//■うまくいくソース■
//PDOオブジェクト生成
$dbh = new PDO('mysql:host=localhost;dbname=takeblizzard',
  'takeblizzard','●●●●●');

//文字コードのセット
$sql = <<< ENDSQL
set character set sjis
ENDSQL;
$sth = $dbh->prepare($sql);
$sth->execute();

//SQL文の準備
$sql = <<< ENDSQL
select item_name
from item
where item_id < 100
ENDSQL;

//SQL文の実行
$sth = $dbh->prepare($sql);
$sth->execute();

//結果をフェッチして表示
while($data = $sth->fetch(PDO::FETCH_ASSOC)){
  echo mb_convert_encoding($data['item_name'],
      "EUC-JP","SJIS").'<br />';
}

 そしてその表示結果。

くずかご
オークテーブル
オークベッド
タルタルデスク
ブロンズベッド
マホガニーベッド
メープルテーブル
作業台
錆びたバケツ
粗末な寝台
スクレテール
見台
タルタルスツール
文机
チェスト
コッファー
(以下省略)

 やっとうまくいったぜ。

2009年9月12日 (土)

コマンドラインでMySQL勉強中

 Snow LeopardでMySQLの勉強の2回目。今回はコマンドライン上でMySQLをいじってみたときの記録。内容的にはMac固有の話はなさそうかな・・・

 rootアカウントのパスワード設定までは省略。以下、自分用のアカウント設定からDBオブジェクト作成、テーブル作成、データ読み込みまでやってみた。

■まずrootで接続してみる

$ /usr/local/mysql/bin/mysql -u root -p
Enter password:●●●●●●●●●●●
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.0.85 MySQL Community Server (GPL)

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

接続できた。次にどんなDBが存在しているかの確認。show databaseコマンドを使う。以下は散々いじった後にものなので、インストール直後とはちょっと違うかも。

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema | 
| mysql              | 
+--------------------+
2 rows in set (0.02 sec)

■自分用のDBを作ってみる

create databaseコマンドで行う。

mysql> create database takeblizzard;
Query OK, 1 row affected (0.02 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| takeblizzard       |
+--------------------+
3 rows in set (0.02 sec)

ちゃんと追加されているな。

■自分用のアカウントを作成

 ここで自分のアカウントを作成。作成するときに、このアカウントをどこのDBにアクセスさせるのかを言うので、先にDBを作ったってわけ。

 アカウントの作成はgrantコマンドを使う。

mysql> grant all privileges on takeblizzard.*
-> to takeblizzard@localhost identified by '********';

 all privilegesは(ほぼ)すべての権利を意味していて、on takeblizzard.*っていうのはtakeblizzardっていうDB内のすべてのテーブルに対してという意味、to takeblizzard@localhostはlocalhostからアクセスするtakeblizzardっていうアカウントに権利を与えるって意味で、identified byはパスワードの設定。

 で、作った後は必ず

mysql> flush privileges;
Query OK, 0 rows affected (0.08 sec)

 というコマンドを実行しておかないといけないらしい。

■自分のアカウントで接続しなおし

 rootでの作業が終わったらさっさと切断して、自分のアカウントで入り直す。

mysql> exit
Bye

$ /usr/local/mysql/bin/mysql -u takeblizzard -p
Enter password: ●●●●●●●●
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.0.85 MySQL Community Server (GPL)

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

 おーできたできた。

■DBの中にテーブルを作ってみる

 まず、使用するDBを言っとく必要があるらしい。

mysql> use takeblizzard
Database changed
mysql>

 まずDBの中にどんなテーブルがあるか覗いてみる。

mysql> show tables;
Empty set (0.00 sec)

 空っぽだw 当たり前か・・・

 テーブルの作成はcreate tableコマンドを使用する。敢えて何のテーブルかは言わないが、知る人ぞ知る・・・ライセンス的には大丈夫だよな。一応確認したつもりだが。

mysql> create table ITEM(
    ->  ITEM_ID int(10) NOT NULL,
    ->  ITEM_NAME varchar(32) NOT NULL,
    ->  ITEM_TEXT text,
    ->  IS_RARE enum('0', '1'),
    ->  IS_EX enum('0', '1'),
    ->  STACK_AMOUNT int(10),
    ->  ITEM_NAME_ENGLISH varchar(32),
    ->  INSERTED_DATE datetime,
    ->  UPDATED_DATE datetime,
    ->  ITEM_TEXT_ENGLISH text
    -> );
Query OK, 0 rows affected (0.02 sec)

 行頭が「->」で始まってるのは、コマンドの途中で改行を入れたってことを示している。コマンドの終わりはセミコロンで認識するらしく、セミコロンが入らない限り一つのコマンドを複数行に分けて入力できるらしい。

 コマンドの指定内容の方は、テーブル名がITEM、以下かっこの後に各列の名前・値の型を指定してカンマで区切っていく。例えば一つ目の列の名前はITEM_IDで値の型はint型。型の後のかっこで括られた数字は桁数か? あとNOT NULLとつけることでその列には必ず値を入れるよう制限する。

 列の指定が終わったらかっこで閉じてセミコロン+Enterキー。これでテーブルが作られた。入力できる値が決められているExcelシートを用意するようなもの・・・というと誤解がありすぎるんだろうか。でも俺的にはそんな風に理解してしまった。

 作ったテーブルの列定義状況を見ことができるらしい。show columnsコマンドを使用する。

mysql> show columns from item;
+-------------------+---------------+------+-----+---------+-------+
| Field             | Type          | Null | Key | Default | Extra |
+-------------------+---------------+------+-----+---------+-------+
| ITEM_ID           | int(10)       | NO   |     | NULL    |       |
| ITEM_NAME         | varchar(32)   | NO   |     | NULL    |       |
| ITEM_TEXT         | text          | YES  |     | NULL    |       |
| IS_RARE           | enum('0','1') | YES  |     | NULL    |       |
| IS_EX             | enum('0','1') | YES  |     | NULL    |       |
| STACK_AMOUNT      | int(10)       | YES  |     | NULL    |       |
| ITEM_NAME_ENGLISH | varchar(32)   | YES  |     | NULL    |       |
| INSERTED_DATE     | datetime      | YES  |     | NULL    |       |
| UPDATED_DATE      | datetime      | YES  |     | NULL    |       |
| ITEM_TEXT_ENGLISH | text          | YES  |     | NULL    |       |
+-------------------+---------------+------+-----+---------+-------+
10 rows in set (0.00 sec)

■ファイルからデータを取り込む

 ここで作ったテーブルにタブ区切りのデータファイルを読み込ませる方法があるらしいのでやってみた。

 ちなみにファイルのエンコードはシフトJIS。もちろん先ほど定義したテーブルの各列と型が一致するデータとなっている。

 データを取り込むためのコマンドはload dataコマンド。

mysql> load data local infile
-> '/full/path/name/item.20090801.txt' into table item;
Query OK, 9212 rows affected, 4862 warnings (0.07 sec)
Records: 9212  Deleted: 0  Skipped: 0  Warnings: 4523

 読み込めたのは読み込めたらしいが、なんか5000個弱のワーニングが出てるな・・・

 早速テーブルの中身を見てみる。データを見るにはselectコマンドを使う。

mysql> select item_name from item where item_id <10;
+------------------+
| item_name        |
+------------------+
| ?I?[?N?x?b?h     |
| ?u?????Y?x?b?h   |
| ?}?z?K?j?[?x?b?h |
| ?e???ȐQ??       |
| ?m?[?u???x?b?h   |
| ???C?????x?b?h   |
| ?S?[???h?x?b?h   |
| ?`???R?{?̐Q???? |
+------------------+
8 rows in set (0.00 sec)

 コマンドの意味は、selectコマンドの後に内容を見たい列の名前、fromでテーブルの名前、whereで条件を指定する。ここではitem_id列に入っている値が10以下という条件だ。

 まぁそれはいいとして、全力で文字化けしてるんだがorz

■文字化けへの対処

 ヤホーでもゴーグルでも((c)ナイツ)いいんだが「mysql load 文字化け」で検索するといろいろ出てくる。読み漁ってみると、どうもDB側の文字コードとデータファイルの文字コードが合わないといけないらしい。

 ここはファイルのエンコード(つまりSJIS)に合わせてみることにした。

 まずターミナルのエンコードをSJISに設定しておく。

 次にDBのデフォルト文字コードなんだが、create databaseするときに指定できるらしいので、一回DBを削除して作り直す。この工程はrootでやっとくのが無難そうな気がしたので、rootで接続しなおした。DBの削除はdropコマンド。

mysql> drop database takeblizzard;
Query OK, 1 row affected (0.47 sec)

 で、もう一回create databaseするのだが、今度はdefault character set sjisをつけておく。

mysql> create database takeblizzard default character set sjis;

 ここでまた自分のアカウントで接続してテーブルを作り直し。

 テーブルを作ったらこのコマンドを入力しておく。

mysql> set character set sjis;
Query OK, 0 rows affected (0.00 sec)

 その上でもう一度load dataコマンドを投入。

 再度selectで中身を見てみると・・・

mysql> select item_name from item where item_id <10;
+------------------+
| item_name        |
+------------------+
| オークベッド     |
| ブロンズベッド   |
| マホガニーベッド |
| 粗末な寝台       |
| ノーブルベッド   |
| ロイヤルベッド   |
| ゴールドベッド   |
| チョコボの寝ワラ |
+------------------+
8 rows in set (0.00 sec)

 おーなんとかできたぞ。これでいいのか?自信ねぇなぁ・・・

2009年1月 1日 (木)

KompoZerでソースに意図しない改行が入る

 前々回に書いたMacOSXでも動作する使い勝手のよさそうなHTMLエディタ「KompoZer」だが、使っている内に一つ問題が出てきた。

 WYSIWYG編集をした後にソースを編集しようとしてソース画面を開くと、ソース内の意図しないところに勝手に改行が入るのだ。KompoZerで作成した記事をそのままココログの記事投稿に突っ込むと、ソースで改行されているところは空白として表示されてしまう。なんか気持ち悪い、、、

 早速ネットで検索。KompoZerではHTMLソース上勝手に改行が入るとの記事をいくつか見つけることができた。NvuやNetscape Comopserではこういう動作はしていなかったらしい。

 さらに見ていくと、回避手段に関する記事を見つけることができた。

 インストールされているファイルの内、「editor.js」、「all.js」について、以下のような書き換えをすることで回避できると書いてある。

  • 【修正前】pref("editor.htmlWrapColumn", 72);
  • 【修正後】pref("editor.htmlWrapColumn", 1000);

 つまり、勝手に72バイト目のところで改行していたということなのか。この変更で、1000バイト目で勝手に改行するようになるという話のようだ。

 記事によると、この対処をすることでXHTMLならば勝手な改行が入らなくなると書いてあった。今日は実は仕事先のためWindowsしか手元に環境が無いので、この回避方法をWindowsで試してみた。

 が、結果はXHTMLのTransitional/Strict共にNG。何がいけないのやら。

 これとは別に、Seamonkeyを利用して回避している人の記事も見つけた。それも早速インストールして検証開始(Windowsで)。

 SeamonkeyのComposer設定内で「ソースの変更を最小限に抑える」のチェックボックスをオンにすると余計な改行はしなくなる。が、惜しいのはSeamonkeyのComposerはHTML4のみ。

 あとはMozilla_Correctというアプリを使って整形する手段くらい。ソースを保存後にこいつを通すと綺麗に整形してくれるそうだ(未検証)。KompoZer単体ではもうどうしようもないらしい。

 だんだんホームページビルダーが恋しくなってきたが、ここはガマン。このくらいの手間ならまだなんてことはない。

2008年12月23日 (火)

Win→Mac移行作業、格闘中

 前回Macに乗り換えた話を書いた。

 俺自身は今までずっとMicrosoft漬けだった人間だ。正直Macを使うのは生まれて初めてと言ってもいいくらいだ。店頭で触った程度の経験しかない。

 そんな人がMacを初めて使うときの右往左往振りってどんなもんかネットで検索してみたんだけど、これが意外と少ない。あまり困った人はいなかった?それともWindowsからMacに移行する人はあまりいない?

 というわけで、現在格闘中の様子を書いてみる。一部原因が俺の調査不足によるものがあったり、好みや偏見から来るものもあったりするかもしれないので、ご了承いただきたい。

■マウス

 Macのマウスは伝統的に1ボタンだとばっかり思っていた。いや実際購入したMacに付属しているマウスもぱっと見は1ボタン+ホイール替わりの小さなトラックボールに見えるんだが、操作している内に実はそのマウス2ボタン+トラックボールであることが判明した。Macはこだわりを捨ててしまったのかと思った瞬間である(好き嫌いは別にして)。

 ただし、デフォルトでは両ボタンとも左ボタン(Mac流に言うと主ボタン)に設定されていて、2ボタンマウスとして使うのであれば右ボタンを副ボタンにする設定をしなければならない(だったと思う、確か)。設定さえすれば、例えばインターネットブラウザのリンク上で左ボタンをクリックするとリンク先にジャンプ、右ボタンをクリックするとポップアップメニューという、Windowsと変わらない動作をしてくれる。

■キーボード

 これがいまだに良く理解できていない。調べた結果を書くと、Windowsでは「Ctrl」「Alt」に相当するキーが、Macでは「command」「option」というキーになるらしい。

 さらにMacのキーボード上には「command」「option」以外に「control」というキーも存在しているのだが、この「control」キーは「command」「option」とは別物らしく、WindowsのCtrlキー相当と思ってはいけないらしい。

 さらにもう一つ言うと、この「command」「option」 の二つのキーは、キーボードの左下のところ並びが左から「option」「command」となっている。Windowsでは左から「Ctrl」「Alt」で、機能的にはお互いの順番は逆になっている。

 ここは慣れなんだろうなあ・・・。

■ソフトのインストールとファイル形式「*.dmg」

 Windowsで利用していたときと同様に、やはりかなりの部分でフリーウェアのお世話になる。欲しいものを見つけてダウンロードしようとすると、たまにWindowsのときには見慣れない拡張子のファイルに出会う。それが「*.dmg」だ。

 調べてみるとこのファイル、OS Xでサポートされたディスクイメージファイルだそうで、最近ではこの形式でプログラムを配布することが多いそうだ。

 dmgファイルをダブルクリックして実行(?)というか開くと、仮想ディスクとしてマウントされてデスクトップ上に他のディスクのアイコンと一緒に並ぶ。で、マウントされたと同時にその内容が開く。

 開いた中には大抵、実行ファイル(*.app)と、それに付随するドキュメント等、あと気が利いている場合にはアプリケーションフォルダへのショートカットが入っており、インストール作業としては、実行ファイルをアプリケーションフォルダのショートカット上にドラッグ&ドロップするだけという操作になる。

 これが最初まったく理解できなかった。

 とりあえずdmgファイルをダウンロードできたのでダブルクリックするのだが、Windowsのmsiファイルのようにインストーラが起動するわけでも無く、いきなりディスクイメージの内容が表示されるだけなのである。「どうしろって言うんだ」と一瞬悩んだ。

 dmgファイルを開いた中には実際appファイルが存在し、そいつをダブルクリックすると実行できてしまう。そこで、「なーんだインストーラ不要でexeファイルのみみたいなもんか」と思い、そいつを直接Dock(Windowsで言えばタスクバーに付いてるクイックパネルみたいなもんか?)にドラッグ&ドロップして使っていたら、実行する毎にdmgファイルをマウントするし、dmgファイルをごみ箱に捨てたらDock上のアイコンが「?」マークに変わって起動できなくなったりとテンヤワンヤ。

 ややしばらくして「あーappファイルを一度アプリケーションフォルダにコピーしろって言ってるわけね」というところにやっとたどり着いた。

 一部専用のインストーラが付いている場合もあるが、大抵はappファイル、もしくはappファイルを含むフォルダを丸ごとアプリケーションフォルダにコピーしてインストールは完了するらしい。

 それはすなわち、Windowsのようなインストーラや、プログラムの追加と削除みたいな機能がMacには無いというところにもつながっている。Macでのアンインストールは、appファイルをごみ箱に捨てるだけ。なんかやたらゴミが残りそうな気がするんだが・・・。

■インターネットブラウザ

 Windowsでは主にSleipnirを使用していた。他にGraniLunascapeFirefoxOperaSafariGoogle Chromeなんかもインストールだけはしてあったという状態。

 Macには標準でSafariが付属しているが、前からの流れでFirefoxとOperaをインストールしてみた。IE系のブラウザはこの機会におさらばして、主にFirefoxを使うことにした。

 今一つ不満に思っているのは、リンクに対してマウスの中クリック(ホイールボタンのクリック)で強制新規タブ表示にしていたのだが、Macではどうもそれができないようで、commandキーを押しながらの左クリックか、もしくは右クリックメニューから「新規タブに開く」を選択のどちらかで回避中。

 実は中クリック自体はOSとしてDashboardの表示に割り当たっており、この問題はMac上のどのブラウザを使っても同様だと思う。

 これについてはいまのところ我慢しているという状態。

**2009-02-07 訂正**

 Windowsで使っていたマウスに付け替えてしまったときの、マウスの設定を勘違いしていたことがわかった。ちゃんと設定すれば中クリックを使用可能。解決方法をこちらに書いた。

**訂正終わり**

■メールクライアント

 Windowsでは秀丸ールを使っていた。が、もちろん秀丸ールにMac版は存在しない。

 標準でMailというアプリが付属しているが、他に選択肢はないものかとネット上を探してみた。しかし、そもそもメジャーなメールクライアントソフトが存在しない?ように見える。

 記事として見つけたのはThunderbirdEudoraくらい。Eudoraは販売が終わってしまったようなので、Macを使っている人のメールクライアントは、ほぼ標準のMail.appかThunderbirdの2択なのだろうか。

 そんなわけでThunderbirdを使うことにした。Thunderbird自体はWindowsのときと同じ操作感で使えたので問題なし。ただ、秀丸メールとの機能の違いは目をつぶらざるを得ない所がある。例えば、

  • 秀丸メールではメール一覧を2階建て表示していたが、Thunderbirdでは1階建てオンリー
    →これはどうにも回避できなさそう。
  • 秀丸メールでは一度振り分けせずにメールを受信、全部参照して既読にしてから手動で一括振り分けをしていた。Thunderbirdでは受信即振り分けしかできない模様。
    →未読のまま振り分けてしまっても、未読だけ追いかける機能で代用。それでも受信後に一度参照して既読にしてしまった直後にもう一度見るのは難しいが。

■テキストエディタ

 Windowsでは秀丸を使用していた。俺が秀丸に求めていた機能は、

  • マルチエンコードの自動判定読み込み機能。ShiftJIS、EUCJP、UTF-8を頻繁に利用。
  • マルチ改行コード(?)の自動判定読み込み機能。CR+LF、LFのみを頻繁に利用。
  • アウトライン解析結果表示枠。例えばphpのスクリプトを書いている時に、同表示枠に定義した関数の一覧が表示され、クリックするとその場所に飛んでくれる。
  • キーボード操作の記録と秀丸マクロ。単純に操作の記録再生から、マクロファイルへの書き出しや自由な変更もできるので、かなり大助かり。
  • Hidemarnet Explorerのftp機能。秀丸のファイルを開くダイアログからftpで直接サーバに存在するファイルを開いたり保存したりできる。サーバ上にあるファイル更新にとても重宝している。
  • 構文解析と色分け機能。
  • 強力なキーボードカストマイズ機能。MS-DOS時代にMIFESというソフトを使っていたことがあり、おそらく俺の手がMIFES風のキーバインドを要求している気がする。

 この機能を包含しているMac用のテキストエディタを探してみたが、「mi」というテキストエディタが一番よさげ。ほぼ希望の機能は全てありそう。ただ、まだ全部検証しきれていないが、たぶん将来もこれを使うことになるだろう。

■Telnet/SSHクライアント

 WindowsではPoderosaTeraTermを使用しているが、それに相当するソフトがMacでは見当たらない。

 Macではコマンドラインから普通にtelnetコマンドやsshコマンドを使用するのが一般的らしい。俺が今よく使っているCentOSへのログインも普通にできて、使用感もまったく問題無かったため、コマンドラインで代用することにした。

■ftpクライアント

 WindowsではFFFTPFileZillaを使っている。Mac用として「Cyberduckというソフトが一般的」と雑誌に書いてあったのを見てとりあえずそれをインストールしているが、FileZillaにもOSX用のものがあるようで、これから検証する予定。

 たぶんFileZilla使うことになりそうだな。

■HTMLエディタ

 Windowsでは市販ソフトのホームページビルダーを使用していた。求めていた機能としては、

  • WYSWYG編集ができて、ソースの編集もできる。できればリアルタイムにHTMLレンダリングしてくれると嬉しい。
  • HTML4.0とXHTML1.0/1.1に対応してる。
  • CSSも書ける。覚えきれないので設定ダイアログからCSSを創成してくれると嬉しい。
  • 文字エンコードにSJIS、EUC、unicodeに対応している。

 こんな所だろうか。最初の書き出しはWYSWYGで軽く書いて、細かいところをソースで直接タグ編集しながら仕上げるというやりかたをすることが多い。さらにその先としてphpの作り込み等は秀丸に切り換えて行なう。

 ブログの記事なんかも、最初は秀丸で書いて、その後ホームページビルダーに突っ込んで細かいタグを調整して、HTMLソースの方をブログにアップしたりしている。ココログがXHTMLベースなため、この作業のときは意図的にXHTMLモードに変更していたりする。

 で、これを満たすMac版ソフトを探しているがまだ見つけられていない。この記事を書きながら調べてみたら、hpDrafterが近そうか。後日検証予定。

■FF11関連

 まだちょくちょくログインしているFINAL FANTASY XI Online。ゲームをしながら、横のPCでばななえふえふじゅういち時間Moogle for Windowsをよく使っていた。これに相当するMac版のソフトがどうも無さそう。探せばWeb版があるのだろうが、使い心地の点では独立したアプリの方がいいなあと思う。

 現状は、タイマーに関しては一般的なタイマーソフトを探してきてインストールした。そのほかは必要なときにVMWare Fusion配下でWindowsを起動して、そこでばなな等のツールを使っている。

 この辺は開発環境の勉強と合わせて自作する方向を考えてもいいのかもしれない。

■イメージビュアー

 WindowsではIrfan Viewを使用している。求めている機能は、

  • 大量画像ファイルのサムネイル表示
  • サムネイル画面から外部画像編集ソフトの直接起動

 この大量画像ファイルとは、FF11で撮ったスクリーンショットのことである(笑)。

 FF11をウインドウモードで使っていて、かつ標準のSS機能ではなく外部ツールで全画面SSを撮る機能を使っているため、FF11の日記を書いたついでにSSをアップするときなどはFF11の画面だけ切り出すために必ず画像編集が必要となる。

 それでなくても他人の名前にボカシを入れたり関係無いメッセージ部分をボカシたりとかするためにも画像編集は必要だし、ヴァナ時間の夜中に撮ったSSなどは暗すぎて明暗調整をしないと見えなかったりするので、なんにしても必要だ。

 サムネイルで全体を眺めながらこれだと思う画像を選択し、即画像編集ソフトを起動して編集作業に入れるというのが条件。実はこういうMac版ソフトがまだ見つけられてなかったりする。

 サムネイル自体は標準のFinderでもできるらしい。また、専用のソフトとして絵箱というソフトも使ってみたりしている。外部ソフトの起動はできないが、なかなか使い心地はよさ気。検証を継続する予定。

 今見てみたら、絵箱に画像を編集する機能が付いてる・・・? それならそれでもいいなぁ(笑)

■開発環境

 Windowsでは、最近は主にMicrosoftのVisual C#で作っていたのだが、もちろんこれのMac版は存在しない。

 というか、せっかくMacにしたのだからMacの開発環境の勉強をあらためて1からやろうという気分でいる。まだ雲をつかむような話ばかりに見えているが、キーワード的にはXcode、Cocoa、Objective-C、AppleScriptってな感じなんだろう。全てこれから勉強する予定。

■他にもいろいろ

 ファイラーとして、WindowsではFileVisorを使用していたのだが、Macではまだ探し始めていない。ただFinderではいまいち操作性に満足できていないので、いずれ必ず代用ソフトは探してみたいと思う。

 あとアプリケーションランチャー。WindowsではKick inというソフトを使用している。それ以外にタスクバーのクイックパネルも活用している。クイックパネルの部分はDockに任せるとして、このKick inに相当する様なアプリがあればいいのだが、まだ探し始めていない。

 この辺はまたそのうち探そうと思う。


 こんな感じで、着々と環境が整いつつある。今のところ料金支払いが発生しないものを中心に探しているが、いよいよ本格的にMac使う覚悟ができたときはシェアウェアも探してみてもいいと思っている。

 とにかく探すのに苦労はするが、探せばなんとか以前と同じような作業環境を整えるのは、不可能では無い気がしてきた。

 いずれ開発環境の勉強の成果も報告できればと思う。

マックに乗り換えてみた

 知り合いの影響を受け、深く考えずに一昨日WindowsからiMacに乗り換えてしまった。

 「どうせネット見てメール見るくらいだし」とか「最悪MacでWindows動かせるし」とか言われて、確かにそうかなぁと思ったのが運の尽き(?)。現在環境を整えている最中だが、一部ソフトで代用が利かなそうなものがいくつかでてきている。

 まぁ言う通り、「最悪MacでWindows」で回避する方向で検討中。

 Macというと、洗練されたGUIや表示の綺麗さといったイメージがある一方、使う人はセンスがある人、またはその道のプロという固定観念を持っていたりする。つまりは、果たして俺に使いこなせるのかという不安だ。

 あと、熱狂的なファンが多いというイメージもあるな。それと、WindowsやMicrosoftを目の敵にしているというイメージも。

 俺が学生の頃だからもう10数年前のことになるが、Mac使いの友人から「Macはパソコン(PC)じゃない。パソコンとはIBMが作ったあのIBM-PCのことを言うんだ。Macに対してパソコンという言葉を使うべきではない。」なんてことを言われたことがある。

 その頃も俺はコンピュータに関してほとんど無知だったので「ふーん」くらいなもんだったが、実際の所いまWikipediaで「iMac」を調べてみると、「パーソナルコンピュータ」だという解説がついていたりする。一世代前のCPUもPower"PC"だったわけで・・・。この辺、今はあまりこだわる必要はないんだろうか。

 あと印象に残っているのは、1ボタンマウスだろうか。当時MS-DOSで使用するマウスが2ボタンマウスだったのに対してMacは1ボタンしか無いのを見て不思議な感じがしたと同時に、なんとなくかっこよさも感じていた気がする。これも、友人はやたら自慢していたっけな・・・。

 会社に入社してから、別の友人にMacでの開発について聞いたことがある。その友人は別にMac使いというわけではなく、仕事上必要になったのでMacでの開発について調べたのだそうだ。

 曰く、言語はObjective-Cを使っていること。アプリのインターフェース作成に関するお作法的な物があり、それに従っていないといろんな人から叩かれると(意外とちゃんと作れない人が多いらしい)いうことを話してくれた記憶がある。

 こんな所からも、MacユーザーとはMacに対する愛が深い人たちなのだと認識していた。

 俺にとってMacを購入することはすなわち、そんなMacの世界に飛び込むんだということを意味していた。正直その壁は高かった。そんなところに、記事の冒頭のように言われてしまって「もっと気楽でいいのか」などと思ってしまったのだ。

 こんな風に書いてはいるが、購入したことを後悔しているのかといえばそうでもなく、実際触ってみるとこれはこれでいいなと思える。なんせ綺麗だし。購入したのはiMacだったのだが、価格もそう高くもなく、その割に解像度の高いディスプレイ付きの一体型で、しかも音も静かときている。俺が持っていた不安もかなり払拭してくれる。

 なのでしばらく根気よく使ってみたいと思う。

2008年9月 6日 (土)

またProxyと格闘 Chromeインストール編

 GoogleからオープンソースのWebブラウザ「Google Chrome」のβ版が公開された。

 試しにいくつかの社内サイトにアクセスしてみると、噂に違わず動作は軽快な気がする。ネットの記事を見るとJavaScriptの高速化を図ったと書いてあるが、JavaScript以前に起動とかJavaScriptを使用していないページでもさくさく動いてくれるような気がする。これは機能がシンプルな分なのかな。正直シンプルすぎて使いづらいと感じもしなくもないけど。

 そんなことはさておいて、今日書こうとしているのはGoogle Chromeをインストールしようとしたときのお話。以前にも少し書いたProxy認証の話である。

 社内でWebページを提供している立場上、作ったページはある程度いろんなWebブラウザで表示確認をしてみる。

 社内の利用者の大半はIE(及びIEエンジンを利用したブラウザ)か、少数派ながらFireFoxといった感じ。確認する上では一応レンダリングエンジン毎に用意すればいいよなぁと考えて、IE、FireFox、利用者は少ないけどOpera、Safariを自分の端末にインストールしてある。

 そういう意味では今回のGoogle ChromeはSafariと同じくWebKitというエンジンを使っているらしい(Wikipediaにはそう書いてあったという程度の知識だけど)ので敢えてインストールすることも無いんだが、一応見ておきたい。

■まずはインストーラを入手

http://www.google.jp/chrome/

■インストーラの起動

 ChromeSetup.exeを起動する。すぐに

「インターネットへ接続しています」

 というダイアログが表示されるが、その後

「インストールに失敗しました。コンピュータがインターネットに接続されていることと、ファイアウォールでGoogleUpdate.exeが接続許可されていることを確認して、もう一度お試しください。エラーコード=0x80042197」

 と出てくる。それと同時にブラウザが起動されて、Googleのインストーラヘルプセンターのページが表示される(英語)。どうもエラーの内容を説明したものらしく、それによるとイベントビュアーのアプリケーションのページを見ろとのこと。

 イベントビュアーにあった該当のログは次のような感じ。

Network Request Error.
Error: 0x80042197. Http status code: 407.
Url=https://tools.google.com/service/update2 Trying config: source=IE,
named proxy=会社で使っているproxy:proxyのポート・・・<local>.

 ってな感じ。HTTPステータスコードの407は「Proxy Authentication Required」。Proxyの認証が必要ですってこと。インストーラは認証Proxyには対応していないらしい。

 またProxyかよ(´・ω・`) ってことだ。

 以前にも書いた通り、うちの会社では社外のインターネットにアクセスするには会社が用意したProxyサーバを利用する必要があり、Proxyを利用する際にIDとパスワードが必要となる。前に書いたときはLinux上の話だったが、今回はWindowsXPが舞台である。

■さてどうしたものか

 インストーラそのものに参照するProxyを設定するようなところは無かった。もしかしてコマンドラインで指定すればできるのか知らないけど、発表されたばっかりだとそういう情報も少ないんだろうし、その線は置いておく。

 以前、先輩から聞いた話で、「自分のPCの中でProxyサーバ立ち上げて、そいつに認証代行させちゃったら?」なんてことを言われたことがある。確かにその方法を使えば、Chromeインストーラのように認証Proxyに対応していないアプリでも、そいつ自身が認証をすることなく動作することはできそうだ。

 で、ネットを探し回っていたら、stoneというものを見つけた。アプリケーションレベルのシンプルリピータなんだって。シンプルリピータがどんなものか、いまいちイメージできない・・(‥;) でも使い方を見ると、proxyサーバ機能らしい? この解釈は間違い?^^;

 stoneの作者自身が書いた、stoneを使ったProxy代行認証の記事も見つけた。ここはとにかくこいつでなんとかしてみることにする。

■stoneのダウンロード

こちらのページからいただいた。

http://www.nanno.org/sengoku/stone/Welcome.ja.html

■stoneのインストール

 ダウンロードしたzipファイルを展開すると、exeファイルやdllファイルなどが入ったディレクトリができる。これを適当なところにコピーするだけ。

■stoneの使い方

 起動オプションがたくさんあるのだが、とりあえず今回Proxyの認証代行をしてもらうに当たって必要な作業は次の通り。

(1)コマンドオプションファイルを作る

 コマンドラインで直接オプションを入力できるんだけど、長くなったりするのでファイルにしておくことにする。アクセスするときのポートに合わせて、「8080.cfg」というファイルをstoneと同じディレクトリに作った。

(2)コマンドオプションファイルにオプションを書く

 コマンドオプションファイルの中は、通常stoneに起動オプションとして書く文字列を書いておけばいい。

 便利なのは、コマンドオプションファイルの中身については改行を無視して処理をしてくれること。なのて、読みやすいように改行を入れておくことができる。

 ちなみに、環境は以下を仮定している。あくまでも仮定。中身の値は嘘っぱちだ。

  • proxyサーバ名:proxyserver.co.jp
  • proxyのポート:8000
  • 認証に必要なID:takeblizzard
  • 認証に必要なパスワード:password
  • 自PC内でstoneが待ち受けしてくれるポート:8080
proxyserver.co.jp:8000/proxy
8080 "Proxy-Authorization: Basic dGFrZWJsaXp6YXJkOnBhc3N3b3Jk"
localhost

 1行目の解説は不要か。

 2行目のダブルコーテーションで囲まれた部分は、Proxyの認証を通すためのデータ。後半部分はIDとパスワードを:(コロン)でつないだもの(今回の場合は「takeblizzard:password」)をBASE64でエンコードしたもの。使い回すときはここの部分を適宜変更する必要がある。

 BASE64のエンコードは、vectorやなんかに変換してくれるユーティリティなどがあると思うのでそちらで。

 また、Webページ上でBASE64変換をしてくれるようなページもちらほらあるようだけど、変換するものがIDとパスワードだからねぇ・・・関係無いところに変な足跡残したくはないなんて場合は注意が必要。

 3行目はアクセス許可リスト。ローカルからのアクセスしか許さないんだからー(´д`)

(3)stoneの起動

 コマンドラインから、次のコマンドを入力する。

> stone -C 8080.cfg

 自分はこのコマンドラインが入ってるバッチファイルを用意してしまった。このコマンドラインでさえ忘れてしまいそうなので(´з`)y-~

 起動すると、プロンプトには戻ってこない。ちなみにCtrl-Cも効かないみたい。終わらせるときはコマンドプロンプトごと終わらせる。

(4)IEの設定変更

 これは、どうもChromeインストーラがIEに設定されているProxy情報を元に動いてるっぽいので、IEの設定を変更するという話。

 IEが使用するProxyの設定を、通常利用している認証が必要なProxyから、認証を代行してくれるstoneに切り換えるのだ。

 なので、proxyサーバ名には「localhost」、ポートは8080(これはコマンドオプションファイルの2行目にそう書いたから)を設定する。

 Proxyの設定場所がどこかを書いておく必要は無いよな・・・もともと認証が必要なProxyが設定してあるはずなんだし(-_-)

(5)Chromeインストーラの起動

 これでエラーが出ることなくインストールの処理が進み、Chromeを無事インストールすることができた。

 インストールが終わった後はstoneを終了し、IEの設定も元に戻しておく。

 これをしないと、自PC内の知らないアプリが自分のID、パスワードを使って勝手に外のインターネットと通信し放題になってしまうので、これも注意。

■Chromeを使ってみて

 冒頭にも書いた通り、社内のサイト(認証Proxyを通す必要の無いアクセス)はさくさく動いて問題無い。ところが認証Proxyを通して社外のサイトにアクセスしようとすると、一つのページに何度もIDパスワードを要求するは、途中であきらめて表示しなくなるはと、認証Proxyの考慮はあまりされていないということがよくわかりました。ちゃんちゃん。

 しばらく社内ではChromeのことは忘れることにしよう。うんそうしよう。

バージョン管理=バックアップ?

 先日本屋をぶらぶらしていたら、Subversionの使い方みたいな本に目がとまり、思わず購入してしまった。

入門Subversion―Windows/Linux対応 (上平哲 著 )

 ちなみにSubversionはバージョン管理をするためのツールと言われているものだ。

 そういえば、それはそれは大昔Windowsが発売される前のMS-DOS全盛の頃に、雑誌ASCIIの付録か何かで手に入れたMS-DOS版RCSを使ったことがあったのを思い出した。RCSもバージョン管理ツールの一つで、unix由来のツールということで自分にとってはとても物珍しく興味津々に使っていたのを覚えている。

 あれから十数年 (c)綾小路きみまろみさん

 自分が購入したSubversionの本は、一応初心者向けということになっている。

 この本が面白いなと思ったのは、Subversionをバージョン管理ツールではなくバックアップツールの一種と位置付けて書かれていること。なるほど各リビジョンのバックアップが取れて、しかも任意のリビジョンのリストアが可能なのだから、まぁ簡単に使えるバックアップツールだよな確かに。

 思い返してみると、最近自分の作っているWebスクリプトなんかのバージョン管理などろくにしてなくて、作り込みをするタイミングでその時点のソースを日付付きファイル名で同じディレクトリ内にコピーしておくくらいしかしていない。その結果、似たようなファイル名で日付部分だけ異なるファイルがゾロゾロと並ぶような、そんな状態だ。

 バックアップ行為はしているものの、とにかく美しくない。バージョン管理ツールにはうってつけな状態じゃないか、昔使っていたくせに。と、一人落ち込んでみたり。

 そんなわけで、「バージョン管理ツールはバックアップツール」という考え方、自分にはとても感銘を受けた。

 さて。

 購入したSubversionの本によると、最近注目されているらしいこのSubversionというツールは今風らしく、チーム開発で使えてネットワークにも対応しているんだそうだ。

 とりあえず内容はTortoiseSVNというWindowsで利用可能なSubversionクライアントをベースに説明されているので、早速ダウンロードして使ってみたのだがこれがまた少々癖があるなぁ・・・。差分の表示ができずに、ツールがエラーを出して処理をあきらめてしまう。

 で、ネットで調べてみるとどうも差分を表示する部分がマルチバイトに弱いらしく、実際試してみたファイルも日本語EUCで改行コードLFで保存したファイルだったので、まぁこれが原因なんだろうと推測した。

 その後、日本語に強い差分の外部ツールを探してみたりもしたがどうもめんどくさくなってきたので、RCSに逃げることにした。

 RCSの方はあまりコードとかは気にせず、文字化けは文字化けのままでもとりあえず処理をしてくれる。Subversionのようにチーム開発には向かないらしく、一人で使うのが前提のようだけど、自分の要件としては一人で使うことしかなさそう。RCSが自分にとっては面倒が無くて都合がいい。

 以下、使い方メモ。ググればRCSの使い方はいっぱい出てくると思う。

 まずRCSを使った作業の流れは次の通り。

  1. バージョン管理用に、カレントディレクトリに一個ディレクトリ(名前はRCS)を作る。
  2. バージョン管理したいファイルをコマンドで登録(チェックイン)する。
     →登録すると、登録したファイルはいったん消える。
  3. ファイルを書き換えたりしたいと思ったら、登録済みのファイルからコマンド取り出す(チェックアウト)。
  4. 書き換えが終わったら再びコマンドで登録(チェックイン)する。
     →次のバージョンとして登録される。登録されるとまたファイルは消える。

 あとは、3~4の繰り返し。

 そのほかコマンドで次のようなことができる。

  • 登録した情報(登録日とかそのときのコメントとか)の履歴を見れる。
  • 取り出して書き換えをしている最中は、取り出してからどの部分が書き換えられているかを見ることができる。

 自分が使うのはこんなもんかなぁ。

■チェックインコマンド

ci 登録するファイル名

 このコマンドを実行すると、何か文字を入力するよう求められる。

 最初の登録のときはこのファイルの概要を入力し、そのあとの登録のときはそのリビジョンでの変更点などを入力するといいだろう。

 あとあと、その入力したものが履歴コマンドで一覧としてみることができる。

■チェックアウトコマンド

co 取り出すファイル名

 これだと、読み取り専用状態で取り出される。

co -l 取り出すファイル名

 lはlockのl。書き換える目的で取り出すときに-lを指定する。

 こうすると取り出したファイルは読み書きできる状態で取り出される。ロックされてる間は他の人からチェックインできないらしい。

■登録履歴表示コマンド

rlog 登録したファイル名

 チェックインをしてきた過去の一覧を、チェックインしたときのコメント付きで表示してくれる。

■チェックアウト後の差分表示

rcsdiff 対象ファイル名

 最新版の内容は、一つ前の版からどこが変更されているのかを一覧にして表示してくれる。


 チェックイン・チェックアウトはテキスト/バイナリに関わらずどんなファイルでもできるっぽい。

 差分の表示はテキストでないとあまり意味は無いかな。ググるとWordファイルの差分を表示してくれるdiffコマンドなんてのがあったような気もするけど。

 お手軽にやるならRCSの方がオススメだな。自分的には。

 ちなみに、RCSは以下のページから入手した。

http://www.naney.org/comp/rcs/win32/

2008年7月 5日 (土)

proxy経由のyumとwget

 いま、会社のWindowsXP上でVMWare Playerを使ってCentOS 5.1を構築している。

 構築メモはググればゴロゴロしてるのでいいのだが、会社のネットワーク環境はプロクシ経由でないと外のインターネットにはつなげない。しかもうちの会社は、プロクシを経由するたびにユーザIDとパスワードを要求してくる。

 これにつまずいたのは、yumやwgetをする辺りだった。yumにしてもwgetにしても、proxy経由でインターネットにつなぐには、そのままでは無理だということだ。

 IDとパスワードまで要求するproxyサーバって事例が少し少なめが気がした。それでもググれば見つけられないことも無いはずだが、一応メモっつーことで。

【proxy経由yum】

 「/etc/yum.conf」に、以下の行を追加する。

proxy=http://proxy.server.name:12345/
proxy_username=proxyuser
proxy_password=proxypasswd

 上の例だと、プロクシのサーバー名は「proxy.server.name」、ポートは「12345」、プロクシにアクセスするためのIDは「proxyuser」、パスワードは「proxypasswd」となる。適宜読み替えること。

 ただしこの設定方法は、そのサーバー上で実行するすべてのyumの処理がこの設定で動作することになる。ユーザ毎に設定を変えることもできるらしいのだが、今回はVMWare上で動作するCentOSの話で、自分しか使わないのでこれ以上の調査はやめることにした。

 この値の反映がどのタイミングかはちょっと良くわかんないけど、リブートしておけば大丈夫(ぉ

【GPG key retrieval failed】

ここまで設定してyum installをしようとすると、次のようなエラーが出てきた。

GPG key retrieval failed: [Errno 4] IOError:
 <urlopen error (-2, 'なんちゃらかんちゃら')>

 これに対する対処は、まず「/etc/yum.repos.d/CentOS-Base.repo」というファイルの中に記述されている[centosplus] の「enabled=0」を「enabled=1」に変える。

 そしてもう一つ、キーを設定するために次のコマンドを実行する。

wget http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5
rpm --import RPM-GPG-KEY-CentOS-5

 そうすると、wgetを実行した時点で次のようなメッセージに出会うことになる。

--hh:mm:ss--  http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5
mirror.centos.org をDNSに問いあわせています...
失敗しました: 名前またはサービスが不明です.

 これで、wgetもproxy越えが必要だということに気付く。

【proxy経由wget】

 そこでwgetのproxy越えの方法だが、こちらの方は逆にユーザ毎の設定方法しか見つけられなかった。

 以下の内容で、「~/.wgetrc」というファイルを作るといいらしい。こっちの値は再ログインで反映されるようだ。

http_proxy=http://proxy.server.name:12345/
proxy_user=proxyuser
proxy_passwd=proxypasswd

【再挑戦】

 ここまでやって、再度wgetコマンドとrpmコマンド実行して正常終了を確認。さらにyum installも正常に動作するようになった。


 それにしても、実際ググってみるとproxyに関するこの手のメモが多いこと多いこと。

 まぁ一度しか設定する機会なんて無いし、気持ちはわかるというものだけど。「忘れないようにメモしておく」なんて記事を見ると、「お前もかw」という気持ちにはなる。

 というわけで自分も、忘れないようにメモしt

2008年4月 6日 (日)

C#でヴァナ時間クラス作り直し

 最近Visual C# 2008 Express Editionを入手した。MicrosoftからダウンロードできるフリーのC#開発環境である。

 実際いじってみて、BCBにとても近い感じで使えるのでとても気に入った。

 ただ、これで作ったアプリは最低でも.NET Frameworkの2.0以上が必要になる。しかもデフォルトのまま作ると3.5のアプリを吐き出しやがる。・・・結構会社じゃまだ1.1って人も多いんだが、どういうことかMicrosoftに問い詰める勇気は無い。

 で、C#の勉強がてらというかVC#の環境に慣れるためというか、以前BCBで作っていたヴァナ・ディール時間計算クラスを再作成(移植ではなくて1から)してみた。

 で、それに合わせて記事も少し書き換えした。当時の記事は今のココログではなくてyahooブログで書いていた記事で、ソースとサンプルパイナリをダウンロードできるようにしてあったのだが、ブログの移転のときに置いてきてしまった。なので、この機会にC#版のものをアップしておいた。

 これでようやく体裁は取れたかな。

ヴァナ・ディール時間(クラス作成編)
http://take-blizzard.cocolog-nifty.com/blog/2005/09/post_333b.html

 今改めてネットで調べてみると、当時と比べてヴァナ時間のロジックについて書いた記事が結構見つかる。ツールの方もばなながほとんどメンテ中止状態で、その替わりにいろいろなツールが出回るようになった。ずいぶん時間が経ったんだと思い知らされる。

 そういえばヴァナ時間クラスを作成していたころは、ばななが表示する時間と実際のゲーム画面上の時間が割とピッタリ合っていたものだが、今回の作り直しの際に検証しようと思って比較してみるとずいぶんズレがあって驚いた。

 自分が作ったやつとばななが表示する時間はピッタリ合うのだが・・・。もしかして最近汽船行路の発着がずれるバグって、この辺の問題だったりしないのか?

 そういう自分はアルタナの神兵発売以来、カンパニエバトルまっしぐらだったりする。最近はメリポに必要なポイントも、狩りに出かけて経験値稼ぎが面倒なときも、みんなカンパニエバトルである。一人でも参加できてそこそこ経験値を稼げるところが非常にありがたい。

 で、カンパニエバトル発生までの待ち時間にはVC#でマッタリしながらアプリ作り。最近はずっとそんな生活である。

2007年2月 2日 (金)

「っぽい」

 会社の内部で利用するシステムでの話。

 宛先を間違えてメールを送信すると、普通はどこかのメールサーバから「こんな宛先は無い」ってことでエラーメールが送信者宛に返ってくることがあると思う。

 よく見かけるのは「MAILER-DAEMON」さんからのメール。メールの中身も英語ばっかり・・・いやよく読めば、なぜエラーになったかも書いてはあるけども。で、エラーになった送信メールの内容も添付されていたりする。

 で、先日のこと。普通に送付されてくるメールと、上述したこのMAILER-DAEMONさんのメールを区別して処理しなければならなくなった。

 受信したメールのデータをPerlで書いたスクリプトに標準入力で突っ込んで、MAILER-DAEMONさんからのメールの時だけは送信者に通知する処理をしてほしいというのだ。

 あ、ちなみに言うと、このシステムで送信するとき、送信者アドレスには送信する人の本当のアドレスではなく、システムで統一された一つの代表アドレスがセットされる。普通のメール送信であれば、Fromには自分のアドレスが入り、MAILER-DAEMONさんからのメールも送信者宛に送付されるので何の問題も無いのだが、このシステムはその辺の事情が特殊になっている。

 スクリプトで処理をするのであれば、MAILER-DAEMONさんからのメールを他のメールと区別するため、何か特徴的なところをとらえて判断しなければならない。

 というわけで、ずーっと普通のメールとMAILER-DAEMONさんからのメールを見比べていたのだが、この二つのメールの違いを具体的にあげるのに現在苦慮している。

 人の目で見れば、エラーメールなどはすぐに見分けられる。メーラーデーモン「っぽい」ねって言うのは見ればわかるのだ。

 「そうか。そういうMAILER-DAEMONっぽいところを見つけて判断すればいいんですね」と、自分の隣に座っているY君(仮)。

 ぃゃぃゃその「っぽい」というところをPerlでどう表現するのかが問題なんだってば。

 とりあえず違うところを列挙してみると、

  • 送信者がMAILER-DAEMON
  • 本文が全部英語
  • エラーになった理由は本文に含まれている
  • 送信したメールの本文はエラーメールの添付となっている

 例えば、この送信者にしても「Mail Delivary Subsystem」さんってパターンもあるらしい。「この送信者なら必ずエラーメール」というわけにはいかないようだ。

 メールに添付ファイルが付くこと自体も当たり前なことだから、エラーメール通知との判別には使え無さそう。

 本文が全部英語・・・・・本文内をgrepすりゃいいか?半角英数記号以外が含まれていたら非英語みたいな判定か。依頼者からのメールが英語のみってことはまぁまず無いだろうし・・・たぶん。


 で、この話を考えながらふと思ったのは、例えば自分などはいろんなソフトメーカーのソフトを買ってユーザ登録をして、そのメーカーからの情報メールを受け取っていたりする。

 ソフトメーカーだけじゃない。例えばショッピングサイトであるとか、オンラインゲームの会社からのメールもたくさん来ている。

 そういうものとスパムメールを区別することってできるんだろうか。

 その手のメールとスパムメールの違いなんて、結構紙一重なんだと思う。

 Y君(仮)「おお、確かに!その違いは難しいですね~」

 自分が必要なメールは普通のメールで、必要無いメールはスパムメールになるんだ。

 例えばよくファイアーウォールソフトについているスパムメールブロック機能であるとか、あとniftyのメールでも学習型のスパムメールブロック機能なんてのがある。あと、秀丸メールにもそんな機能があったなぁ・・・。

 たしかどっかで聞いた話だと、ああいうのはスパムメールに含まれがちな単語の辞書を持ってて、その単語の出現頻度で点数を付けて、何点以上だとスパムメールという判断をする仕組みがあるらしい。

 上述した「っぽい」をプログラムで具現化した一つの形なんだろう。

 Y君(仮)曰く、「そういうのを考えるの苦手なんですよね~」。

 そういう違いを見つけてスクリプト作って、うまく動作したときの快感ったら無いよ? そこが一番おもしろいところだと思うんだけどなぁw

 でもさすがに辞書を作る気にはならない。うん。無理だw

 とりあえず送信者アドレスブロックをすることにした。対処が必要なメールアドレスが新たに出てきたら、その時点で対象に追加する。モグラ叩きだなこりゃ・・・

2007年1月28日 (日)

PS3買いたいけど、さてどうする?

 プレステ関係の買い物・・・例えばゲームに限らず本体とか周辺機器も含めて、自分はいつもPlayStation.comを利用している。

 去年の11月11日に発売になったPLAYSTATION 3だが、当初は品薄を理由にPlayStation.comでの予約・販売は行われていなかった。

 ところが先日PlayStation.com内を見ていたら「好評発売中!」ってどういうことだコラ∑(゜△゜;)

 そのサイトのショッピングインフォメーションを見返して、更にうちに届いていたPlayStation.comからのメールをひっくり返して見ていたら、去年の12月28日に受付開始のメールが・・・

 ってかトピックスの3番目に書くのか?!思いっきり目立たねぇー・・・。

 まぁいいや。そろそろ本格的にPS3の購入を考えねばなるまい。

 早速家の近くのPCショップに足を運んでみた。

 まずはケーブル類かと思い、店の中にあったPS3売り場に行って仕様を確認。ふむ。HDMI端子のケーブルが必要ってか。うちにはHDMI端子付きのAV機器は無いので、必然的に変換ケーブルが必要となる。おーあったあった。HDMI←→DVIケーブル。これならPCのモニターにつなげられそう・・・・ってちょっと待て。モニターにはDVIでPCがつながってる。しかもFF11は続けているからPCからこのモニターをはずすわけにはいかない。いやその前に、うちの白黒テレビ(色が出なくなった古いカラーテレビ)を買い換えるんじゃなかったか?!

 と、まぁ自分がPS3を買った後のことをまったく考えていなかったことに気がついて、結局何も買わずに退散した。

 「今日のところはこの辺でカンベンしておいてやる(゜д゜)」

 誰に向かって言う言葉ではないが、心の中はそんな気分だ。

 そんなわけで、ブログの記事を書きながらPS3の購入計画を立てることにする。

 まずは現状の確認。うちにあるモニター類(テレビを含む)は次の通り。

■パソコンルームにあるモニター

・19インチPC用液晶モニター

 こいつには、DVI接続でPCを、D-Sub15ピン接続でPS2(間にアップスキャンコンバータをかましている)をつないでいる。

・20インチワイド液晶モニター

 こいつには今のところDVI接続でゲーム用PCをつないでいる。今でもたまにFF11をプレイしているので、これをはずすつもりは無い。他にD-Sub15ピンの口が余っている。

 解像度が1680*1050で、おそらくうちの中で一番解像度が高いモニターになるはずだ。

・21型(昔は)カラーテレビ

 1996年製。もはや10年以上となった代物。しかも今やたたいても何しても白黒テレビである。これは早急に買い換えないとならないが、地デジとか言ってるし買い換えのタイミングを計っているところ。

■寝室にあるモニター

・17インチワイド液晶テレビ

 これには現在PS-X(PS2+HDDにテレビチューナーとHDD録画機能が付いていると敢えて言おうw)をつないでいる。ひとしきりパソコンで遊んだ後、布団に潜って寝るまでの間はこいつでオフゲーを楽しんでいる。地デジには未対応。

■PS3を使うシーンを考える

 PS3で遊ぶパターンとしては二つ考えられる。

 一つはパソコンルーム側に置いて、例えばFF11のパーティー誘われ待ちの間にちまちまとやるパターン。

 もう一つは、寝室側に置いて、布団に入ってから眠りに落ちるまでの間じっくり遊ぶパターン。

 まずはこの二つのどちらかを選択しなければ、次に進めない。

 うーん・・・。意外と悩むところではある。

 この二つのシーン、やるゲームにも寄るか? じゃぁ今現在出ているPS3のゲームラインナップを確認してみようか。

 ・・・むぅw

 リッジレーサー7鉄拳5くらいしか無いじゃないかw

 ちょっと待てこんな状態で本当に今買ってもいいのか?w

 あ、思い出した。ブルーレイディスク版のFF7ACもあったっけ。なんか以前のやつに追加シーンもあるんだとか。

 あとLevel5から白騎士物語なんてのも出るらしい。あとはFF13か。いつ発売になるんだいったいw

 当面じっくりやるのは無さそうだし、とりあえずはパソコンルームに置いてちまちまやるんだろうなぁ・・・。ラインナップが揃ってきたら寝室に移してじっくりやるか。

■次にどのモニターにつないで遊ぶかを考える

 パソコンルーム側にPS3を設置する場合の候補は二つ。

 一つは白黒テレビを液晶HDワイドテレビに買い換えて、それにつなぐ方法。

 もう一つはPC側の液晶をワイドに買い換えて、それにつなぐ方法。

 テレビの方は地デジを意識したい。したいんだが、うちの借家は共同アンテナだったりする。なんか噂に聞くと、地デジを楽しむためにはUHFアンテナが東京タワー方面に向いてないとだめなんだそうだ。

 うちは横浜なので、たぶんアンテナも横浜に向いてるんだろうなぁ・・・ってことは、今地デジチューナーを取り付けても見れない可能性がある?

 一方、PC側につなげるにはDVI端子に接続する必要がある。ところが二つのモニターともにDVI-I端子はPCに接続されていて、D-Sub15ピンしか余っていない。買い換えたとしてもこの状況はあまり変わらないと思われる。

■変換ケーブルの話

 HDMIとDSubの変換ケーブルって無いんだろうか。

 そもそもDSubはアナログ信号が流れてて、HDMIはデジタル信号が流れてるんだそうだ。結論としてはピンの対応だけでは変換できないので変換ケーブルは無い、ということらしい。

 世の中には、「HDMI-DVI変換ケーブル」や「DVI-DSub変換ケーブル」というのはあるのだが、実際DVI-Iの端子のうち、半分はデジタル用の端子、残りはアナログ用の端子なんだそうだ。

 つまり、「HDMI-DVI変換ケーブル」と「DVI-DSubケーブル」を組み合わせて、「HDMI-DVI-Dsub」とつなげても信号は届かないらしい。

 DVIのデジタル端子に届いている信号を、DVIのアナログ端子に入力してやれる"何か"が必要ってことだ。

 で、モニターの話に戻ると、PCモニターにPCとPS3をつなげるんであれば、一つしかないDVIの口を二つの機械で共有するためのDVI切替器が必要になる。

■モニター候補

 そんなわけで、今使っている19インチモニターを高解像度ワイド画面の液晶モニターに買い換えるという想定の下、どんなにモニターがいいかネットで調べてみた。

 すると、BENQの「FP241W」という製品を発見。

 24型ワイドでちょっと大きめだが1920x1200でフルHDレベルだし、入力信号はアナログRGB / デジタルDVI-D / S端子 / コンポジット / コンポーネント / HDMIと豊富だし、こいつならDVI切り替えなんて言わずにPS3はHDMI、PCはDVIで素直に接続できて遊べそうだ。口コミ掲示板とかの情報を見てても、やはり同じ事を考えている人がいるらしく、「PS3の接続は」とか「PCと切り替えながら」とかいう記事を見つけたが、内容を見ている限りまず問題は無さそう。

■今日のところの結論

       
  • PS3は当面パソコンルーム側に設置する。
  •    
  • テレビの買い換えはしばらく保留。
  •    
  • PCのモニターをFP241Wに買い換える。
  •    
  • FP241WにPS2/PS3/PCを接続して適当に切り替えながら遊ぶ。
  •    
  • ある程度時期が来たら寝室用に地デジテレビを購入し、PS3をそちらに移動。

 というわけで、来週あたり実行に移す予定。結果はまたブログのネタにしてやろうか。

2007年1月18日 (木)

メールヘッダのエンコードとデコード(Perl)

 最近サーバ上でPerlとPHPでメールを受け取ったり送信したり、メールの内容を見て表示するような仕組みを作った。最初はわからなくていろいろ調べた結果をまとめておく。今回はPerl編。PHP編の投稿時期は未定ということで。

 ただ、きちんとしたルールを厳密に調べながらってわけではなく、ルールを斜め読みして、自分が使ってるメールソフトの動作を見て、たぶんこれならうまく動くというところを見ながらやってみた。

 もしかしたらルールに反していることを書いてるかも。

-メールヘッダ

 メールヘッダにはメールのいろんな情報が入ってる。宛て先、差出人、メールの題名、いつ送信したか、どこのメールサーバを通ってきたか、送信したときに使ったメールソフトは何か等々。

 メールヘッダとメール本文の間は一個の空行(改行だけの行)で区切られる。ファイルの先頭から読み込んで、この空行が出てきた時点でヘッダは終了という判断をする。以後はEOFまでメール本文。

 ヘッダ内の各フィールドは継続行を作ることができる。ある行が前の行からの継続である場合、行の先頭に一個のTab文字を入れておくといいらしい。

 逆にフィルーどの先頭を含む行は必ず先頭から始まっている。

-メールヘッダ内のエンコード形式

 基本的にメールヘッダ内には日本語をそのまま書き込んで送受信することはできないのだそうだ。

 ではどうするのかというと、ASCII文字で表現できる形に変換(エンコード)して書き込んでおく必要があるらしい。書式は次の通り。

  =?<charset>?<method>?<エンコードされたヘッダ文字列>?=

 日本語の場合<charset>には"ISO-2022-JP"が入る。

 このため、エンコード前の日本語文字列はISO-2022-JP、つまりJISコードである必要がある。この他のcharsetを入れたらどうなるのかよく分からないが、どうもメールヘッダ内に日本語を含めるときはJISコードがベースでないとだめらしい。

 <method>には、Quoted-PrintableならQ、BASE64ならBが入る。エンコードの方式の違いで二つを選択することになるが、自分が良く見るのはBASE64だな。

 そして、<エンコードされたヘッダ文字列>にはエンコードされた日本語文字列が入る。

 あまり長くなる行は複数行にわけて処理をしているようだ。その場合には継続行を使っているが、継続させた行単位でエンコードも行なわれている。

 PHPやPerlでメールの送受信や受信したメールの中身を見るには、このMIMEエンコード(特にBASE64エンコード)と、使用中のコードとJISコード間の変換が主な作業になりそう。

 以下、Perl5を想定する。

- JIS -> EUC-JP変換

 jcode.plにお世話になる。$lineという変数にjisコードの文字列を格納しておいて、その文字列をEUC-JPに変換する方法。

   require "jcode.pl";
   jcode::convert(\$line, 'euc', 'jis');

- EUC-JP -> JIS変換
 jcode.plにお世話になる。$lineという変数にEUC->JPコードの文字列を格納しておいて、その文字列をJISに変換する方法。

   require "jcode.pl";
   jcode::convert(\$line, 'jis', 'euc');

- Base64エンコードの方法
 MIMEモジュールにお世話になる。自分が利用していた環境では最初から使えていたような気がする。

    use MIME::Base64;
    $encoded = encode_base64('エンコード元の文字列');

- Base64デコードの方法

 MIMEモジュールにお世話になる。

    use MIME::Base64;
    $decoded = decode_base64('エンコードされている文字列');

- EUC-JPの文字列をヘッダに埋め込むには

 文字列をJISに変換する→Base64でエンコードする→文字列の前に「=?ISO-2022-JP?B?」をくっつける→文字列の最後に「?=」をくっつける。

- ヘッダに埋め込まれてる文字列をEUCに戻すには

 上記と逆をすればいい。

- 文字列の長さについて

 メールのヘッダに入っている文字列は、RFC2047に従うと76バイトまでらしい。が、実際のところはそれ以上でもとりあえずやりとりに問題は発生しない模様。これはメールサーバなりメールクライアントが考慮してくれているからか?

 と、いろいろ調べながらやっていたら、Jcode.pmはもっと簡単にできるらしいという話を見つけた。

 ・・・・もうスクリプト組んじまったじゃないか。

iPhone商標問題。そういえばVistaって。

 アップルが発表したiPhoneに対して、シスコが商標権の侵害を訴えたのだそうだ。ネット上でニュースサイトなんかを巡回していると、最近よくこの話題を目にする。

 それによると、シスコの「iPhone」は2000年から使っているVoIP端末の登録商標なんだとか。それを数年前からアップルが商標取得したいと希望して両者間で話し合いがされていたのが、結局話し合いがまとまることなくアップルが商品の発表に踏み切ってしまったらしい。

 この話について勤め先の社内掲示板に話題が上がっていたのを見た。

 自分自身の知識が不足しており、そもそも以下の話が日本国内の話なのかアメリカの話なのか、そのままアップルとシスコの間の話に当てはまるのかはわからないが・・・。

 曰く、他者の権利を無断で使うことはできないという基本がある。ただ、商標権は特許権、意匠権等と違って、公知という考え方が無い。公知になれば誰でも利用することができる特許権、意匠権等とは違い、商標権はその権利を取った人が使えるという違いがある。

 公知と言う考え方が無いというのは、例えばAという人が使い始めた名称をBという人が見て、Bがその名前の商標権を取ってしまうと、以前から使っていたとしてもAはその名称を使えないということになる。

 自分自身は、この公知という考え方が商標権でもそのまま当てはまるものというイメージがあったので、この話を見て少々驚いた。まぁ自分の無知さ加減が知れるというものだ。

 この話が本当で間違いが無いとすると、アップルはシスコに勝ち目は無いように見えるけど、アップルはどういうつもりなのか・・・。

 この話をふらふらと調べながら見ていたら、Windows Vistaも同じような話があったような気がして、更にネット上を検索してみた。

 こちらは2005年夏の話である。コードネーム"Longhorn"という開発中のOSを、正式に"Windows Vista"にするとマイクロソフトが発表したときに、当時(今はどうか知らないが)マイクロソフト本社の近くにあったVistaという名前の会社が商標権の侵害で訴える可能性があるとしてニュースになっていた

 だがネット上を見ていてもこの件がその後どうなったのかがわからない。その後Vista社が問題無いと判断したのか、はたまた話し合いで解決したのか。今も普通に"Windows Vista"という名前は使われていて、もうそろそろ日本国内でも発売だ。

 どうするかな・・・なによりFF11のVista対応が遅れるっぽい。しばらく自分はVistaに乗り換える予定は無いのだが、まぁそれは今日の本題とは関係の無い話。

 そういえば、自分が愛用している秀丸メールでも同じような話があったことも思い出した。以前は別の名前で公開されていたのが、その名前が他社の商標権を侵害していたのだそうだ。それで今の名前に変更されている。

 案外とこの手の話はごろごろしているものだと再認識した次第。

 今のところ自分の仕事に商標権という言葉が登場する気配は無いが、時が時だ。いつ必要になるかわからんね。

2006年7月14日 (金)

NetVaultでバックアップ取りたいんだけど

 まぁとにかくバックアップ素人の苦悩は未だに続いていたりする。

 とりあえず一週間のうち一回(例えば日曜日とか)にフルバックアップして、その他の日は差分バックアップするようなスケジュールを考えた。で、そう動くようなジョブをマニュアル見ながらそれっぽくスケジュールしてみた・・・。

 でも、なんかジョブをスケジュールしてもエラーで終わってたり、テープ待ちになってたり、わけわからん。

 で、マニュアルじゃなくてネットの方で何かないかと探してみたら、BakBone社のオフィシャル上にこんなものがあったのを見つけた。

 それを見ると、自分がダメダメだったのは次のポイントを押さえてなかったから。

     
  • 1日1回ずつバックアップするなら、7本のテープを用意するのが吉らしい。
  •  
  • テープをちゃんとローテーションするのなら、ラベルを付けるのは必須らしい。
  •  
  • BackupウインドウのTargetタブ内にある「Automatically label blank media」チェックボックスと「Ensure   this backup is the first on the media」チェックボックスはオンに。

 とりあえずそれでもっかいやり直し。週明けまでちゃんと動いてること祈り倒してみる。

2006年6月 9日 (金)

Windows 2003 Serverでインターネット時刻同期

 WinXPとかでインターネット時刻同期をするには、コントロールパネルの中の日付と時刻を開いて、「日付と時刻のプロパティ」ダイアログの中にあるインターネット時刻タブをクリックすると、設定項目が出てくる。

 「自動的にインターネット時刻サーバーと同期する」チェックボックスをONにして、「サーバ」コンボボックスで時刻サーバ名を入力すればいい。

 ところが、Windows 2003 Server(以下、2003サーバ)にはこの設定項目が見あたらない。

 で、いろいろとWeb検索で調べてみた。レジストリいじったりとか、ドメインコントローラがどうのとか、いろいろ出てくることは出てくる。

 まとめてみると次のような感じか。内容がどこまで正しいか定かではない。

概要

 アクティブディレクトリでは、強制的にドメインサーバに時刻同期するようになるんだそうだ。だからインターネット時刻との同期をするための設定項目が無いって事らしい。

 アクティブディレクトリが何なのかを調べる気力が無かったのと、いまいちそこに興味がわかないのと、そもそもドメイン構築目的の2003サーバではなかったというのもあって、ここはこれ以上調べないことにしてしまった。

ntpサーバに同期をさせるための設定

w32tm /config /update /manualpeerlist:NTPサーバ名 /syncfromflags:manual

で、強制的に同期をさせるには次のコマンドを投入。

w32tm /resync

検証しようと思ったら・・・・

 さーてそれじゃぁ日付時刻のプロパティで時間見てみるかと思ってダイアログを開いてみると、インターネット時刻のタブが追加されていた(!!)

 いつ追加されたんだまったく・・・orz

 たぶん、w32tmコマンドを最初に投入したときかなぁ・・・レジストリなんかこれっぽっちもいじる勇気など持ち合わせてないし、そのくらいしかあり得ない(´・ω・`)

 ダイアログ上、時刻合わせをするサーバにはtime.windows.comが指定されていたので、それを社内のntpサーバに変更して設定完了。

なんだかなぁ・・・調べた甲斐がありゃしない。

ユーザによるWeb経由パスワード変更

 apacheのユーティリティであるhtpasswdを使用したアクセス制御の方法については以前に書いた通り。

 その後、その登録したID/パスワードをWeb経由で変更する要件が出てきたので、今日はその辺のお勉強をしてみた。

コマンドライン

 htpasswdコマンドで作成したパスワードファイルに登録されているIDのパスワードを変更するには、次のようなコマンドを実行するとできる。

$ htpasswd -b パスワードファイル名 ID名 新しいパスワード

 このコマンドラインをphpなりCGIスクリプトなりから呼び出して使えば、パスワードの変更が可能である。

 ただよくよく調べてみると、このコマンドを使用すれば他人のIDでも変更できてしまうらしい。単純にIDと新しいパスワードしか入力を求めない。

 ちまたのWeb上でのパスワードの変更でよく見かける、"ID"+"現在のパスワード"+"新しいパスワード"+"新しいパスワード"という形にしたいと思ったとき、htpasswdの機能だけでは明らかに不足している模様。

どう作るか

 やらなければいけないのは、次の処理。

     
  • IDと現在のパスワードで認証OKかを検証する
  •  
  • 確認のため新しいパスワードを2回入力させて間違いがないかどうか検証する
  •  
  • 上記を全てクリアできたときに初めてパスワード変更処理をする

 3点目はhtpasswdコマンドに任せるとして、前2点については自力でがんばる必要がある。

 新しいパスワードを2回入力させて間違いがないかどうかを検証するのは、スクリプトの中でやればいい話で特に問題は無いだろう。

 問題はIDと現在のパスワードで認証OKかを検証する部分。とりあえずパスワードファイルの中身を読んで、現在のパスワードと入力されたパスワードが合致するかを検証するしか無さそうだ。

パスワードファイルの中のパスワード部分

 htpasswdコマンドでパスワードを登録するときに、パスワード自体を暗号化することができる。

 htpasswdコマンドのオペランドを見ると、MD5(-mオプション)、crypt(-dオプション)、SHA(-sオプション)、そしてプレーンテキスト(-pオプション)を指定することができる。ただ、ネットで情報を探していたときに、crypt等は元の文字列が同じでもその都度暗号化された形が異なるので検証が難しいという記事を見かけた。

 実際にやってみた(トリビア風)。

 ちなみに-nオプションは、結果をパスワードファイルではなく標準出力に出力するオプション。

cryptの場合
$ htpasswd -nb user1 pwd1
user1:hRZLxzOTavxS.
$ htpasswd -nb user1 pwd1
user1:mT1rBdicTftqs
$ htpasswd -nb user1 pwd1
user1:0xu8TUF03D9JU
MD5の場合
$ htpasswd -nbm user1 pwd1
user1:$apr1$X32Hy...$9NSx2w6kghT7YBgrRmfvF0
$ htpasswd -nbm user1 pwd1
user1:$apr1$9yq8g/..$yvggLMKh0jAhGPzCi4q7b1
$ htpasswd -nbm user1 pwd1
user1:$apr1$.r/G5/..$zUN2hz36OZ.ORo9sc18t0.

 なるほど、確かにその都度違う文字列になっている。

SHAの場合
$ htpasswd -nbs user1 pwd1
user1:{SHA}pj1LEyqaHTQw+a5QeCX1ckSeDRc=
$ htpasswd -nbs user1 pwd1
user1:{SHA}pj1LEyqaHTQw+a5QeCX1ckSeDRc=
$ htpasswd -nbs user1 pwd1
user1:{SHA}pj1LEyqaHTQw+a5QeCX1ckSeDRc=

 SHAだけは常に同じ暗号化文字列を生成するようだ。

 暗号化文字列が固定にできるなら、いったん現在のパスワードを"htpasswd -nbs"して暗号化した後に、パスワードファイルの中身と直接比較すればいいのかな。

 まぁセキュリティ強度的には少し弱くなりそうだけど、プレーンテキストという選択肢はあり得ないので、これでやるしか無いか。

コーディング
     
  • まず最初に、パスワードを変更させるためのフォームを表示する。(Display()関数呼び出し)
  •  
  • フォームにデータが入力されて送信されてきたら、パスワード変更処理に入る。(ChangePasswd()関数呼び出し)
  •  
  • 入力データをチェックする。各項目の未入力、新しいパスワードの入力不一致があった場合はエラーとする。(ChangePasswd()関数内)
  •  
  • 入力されたアカウントと現在のパスワードが認証OKかを確認する。(CheckPass()関数呼び出し)
  •  
  • CheckPass()関数内では、指定されたパスワードファイルの中から、先頭がIDとして指定された値に一致する物を探し、その行と"htpasswd   -nbs"の結果を比較する。合致していればOK、合致していなければNG、一致する行がなければNG。
  •  
  • 認証OKであれば、"htpasswd -bs"でパスワードを変更する。(ChangePasswd()関数内)

 以下のソースでうまくいったっぽい。exec()関数で実行したコマンドの出力結果を取ってこれなかった($disp変数何も格納されない。コマンドラインでやると実際には何らかのメッセージを出力する)のが解せないけど、まぁ放っておく。

 ぶっちゃけちゃんとした検証はこれからなので、以下のソースの動作保証はしないぞ。

<?xml version="1.0" encoding="EUC-JP"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=EUC-JP" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<title>PHPによるパスワード変更テスト</title>
</head>
<body>
<?php
//---------------------------------------------------------------
//--パラメータエリア---------------------------------------------
//---------------------------------------------------------------
$passwdfile = './test.pass';

//---------------------------------------------------------------
//--パラメータエリアここまで-------------------------------------
//---------------------------------------------------------------

print 'funcは'.$_POST{'func'}.'<br>';
if($_POST{'func'}=='changepasswd'){
    ChangePasswd();
}
else{
    Display();
}
?>
</body>
</html>

<?php
//---------------------------------------------------------------
//--関数---------------------------------------------------------
//---------------------------------------------------------------
function Display()
{/*パスワード変更フォーム表示処理
*/
?>
<div>
<form style="text-align : right;width : 400px;background-color:silver;" method="post">
アカウント名<input size="20" type="text" name="account" /><br />
現在のパスワード<input size="20" type="password" name="nowpass" /><br />
新しいパスワード<input size="20" type="password" name="newpass" /><br />
新しいパスワード(確認)<input size="20" type="password" name="remainpass" /><br />
<input type="submit" value="パスワード変更" /><input type="reset" />
<input type="hidden" name="func" value="changepasswd">
</form>
</div>
<?php
}
//---------------------------------------------------------------
function ChangePasswd()
{/*パスワード変更処理
*/
    global $passwdfile;

    if(strlen($_POST{'account'})==0){
        print "アカウント名を指定してください。<br />";
    }
    elseif(strlen($_POST{'nowpass'})==0){
        print "パスワードを指定してください。<br />";
    }
    elseif(strlen($_POST{'newpass'})==0){
        print "新しいパスワードを指定してください。<br />";
    }
    elseif(strlen($_POST{'remainpass'})==0){
        print "確認のため、新しいパスワードは2回入力してください。<br />";
    }
    elseif($_POST{'newpass'}!=$_POST{'remainpass'}){
        print "新しいパスワードの内容を確認してください。<br />";
    }
    else{
        if(CheckPass('./test.pass',$_POST{'account'},$_POST{'nowpass'})==true){
            //print "パスワードチェックOK<br />";
            $cmdline = '/usr/bin/htpasswd -bs '.
                       $passwdfile.' '.
                       escapeshellarg($_POST{'account'}).' '.
                       escapeshellarg($_POST{'newpass'});
            $disp = array();
            exec($cmdline,$disp,$i);
            if($i==0){
                print 'パスワードを変更しました。<br />';
            }
            else{
                print 'パスワードの変更に失敗しました。<br />';
            }
        }
        else{
             print "現在のパスワードが違います。<br />";
        }
    }
}
//---------------------------------------------------------------
function CheckPass($passfile,$id,$pass)
{/* $passfileに登録されているID:$idのパスワードが$passで合っている
かどうかの検証。以下を前提とする。検証OKならTRUE、NGならFALSEを返す。
- パスワードファイルはhtpasswdコマンドで作成のもの
- パスワードはSHAで暗号化されていること
*/
    $lines = file($passfile);
       
    foreach($lines as $line){
        //改行削除
        $line = rtrim($line);
        //IDの位置検索
        $pos = strpos($line,$id);
        //見つからなかった場合
        if($pos===false){
            continue;
        }
        //先頭に同一IDが見つかった場合
        elseif($pos==0){
            if($line==GetHtpasswdString($id,$pass)){
                return true;
            }
            else{
                //IDが一緒でパスワードが違うなら即return false
                return false;
            }
        }
        //先頭以外に見つかった場合
        else{
             continue;
        }
    }
    return false;
}
//---------------------------------------------------------------
function GetHtpasswdString($id,$pwd)
{/* htpasswd -nbsの結果を返す。
*/
    $cmdline = '/usr/bin/htpasswd -nbs '.
               escapeshellarg($id).' '.
               escapeshellarg($pwd);
    $disp = shell_exec($cmdline);
    $disp = rtrim($disp);
       
    return $disp;
}
//---------------------------------------------------------------
?>

2006年6月 3日 (土)

ジョークツール

成分解析

もう一部ではだいぶ有名な成分解析。

たけBlizzardの成分解析結果 :

たけBlizzardの85%は電波で出来ています。
たけBlizzardの12%は波動で出来ています。
たけBlizzardの2%は真空で出来ています。
たけBlizzardの1%は蛇の抜け殻で出来ています。

 俺っていったい・・・orz

ハリーポッターメーカー

いつもFF11のメンバー登録CGIでお世話になっている瀬野部屋.comさん。ここで、こんな物が公開さていた。

ハリーポッターメーカー
http://www.senobeya.com/hp/test3.html

早速試してみる。

全世界で5194万冊! たけBlizzard シリーズ

第 1 巻 たけBlizzard と 壮健美茶
第 2 巻 たけBlizzard と 市中引き回しの刑
第 3 巻 たけBlizzard と 利益
第 4 巻 たけBlizzard と 得意げ
第 5 巻 たけBlizzard と 芸者接待
第 6 巻 たけBlizzard と ツンデレ
第 7 巻 たけBlizzard と 欲望

 まぁこれは売れねぇわな(´・ω・`)

 ツンデレは好きだけど。

2006年5月31日 (水)

バックアップ環境構築(覚書)

現在構築中のサーバにバックアップ環境を構築する。使用するバックアップソフトはNetVaultというやつ。

箱を開けて内容を確認すると、だいたい以下のような感じ。

     
  • NetVault 7.1用のマニュアル2冊
  •  
  • CDが2枚

で、1枚目のCDの中を見ると、ルートには「netvault700」と「netvault710」というディレクトリが存在している。これはver7.0とver7.1って事か?

更に、CD内にはdocというディレクトリがある。ドキュメントだろうな。これを下に降りていくと、また「netvault700」と「netvault710」というディレクトリが・・・。7.0なんかいらないべ?(´・ω・`)

それらのディレクトリの中身は、PDFのマニュアルだった。アドミニストレーターズガイドとかの他に、アップグレードガイドとか、スタートアップガイドとか・・・。

スタートアップガイドが「netvault700」にはあるけど、「netvault710」には無い。でもアップグレードガイドはある。・・・ってことは7.0をインストールしてから7.1にアップグレードせぇと言うことかぃ。メンドクサー(´¬`;)

■インストール

RHELへのインストール。

1枚目のCDから、まずnetvault7.0をインストールする。/netvault700/linux24/installを起動する。

説明文からライセンス契約まで全て英語なので、応答は適当に(ぉ

そうすると、いつの間にかインストールが終わる(-.-)y-~~

メッセージを見ると、デーモンの起動までやってくれてるようだ。

続いて/netvault710/linux24/installを起動する。

途中の説明文が英語なので、あまり細かく解読はしなかったが、途中で「既にサーバー機能かクライアント機能はインストールされているか?[c s]」みたいな質問があった。cとsは恐らくClientとServerのこと。新規インストールの選択肢が無いと言うことは、完全にアップグレード用なのだろう。7.0をインストールせずに、いきなり7.1のインストールをしても同様の質問だったので、たぶんそうに違いない。

ちなみにデーモンのリスタートもちゃんとしてくれた。えらいえらい。

■ツール類の確認

マニュアル上で説明されているツールはGUI物のツール。Linuxなので、X Windowしないといけないらしい。

起動するツールは主に次の二つ。

     
  • 管理のためのGUIツール (nvgui)
  •  
  • 基本設定をするためのGUIツール (nvconfigurator)

いずれも/usr/binにコピーされるので、パスは通っている。

■仮想ドライブの作成

HDDを出力先にした仮想的なテープ装置を作ることもできるらしい。単独のテープ装置やライブラリ装置も作ることができるみたい。

仮想装置の作成は、NetVault GUIツール(nvgui)で行なう。ポイントは・・・

     
  1. Device Managementウインドウから、メニューバーの「Add」→「AddLibrary」で行けるAdd Libraryウインドウで仮想装置を作成する。
  2.  
  3. この画面でクライアントを右クリックして出てくるポップアップメニュー「Create Virtual Library」をクリックすると、作成用のダイアログが出てくる。
  4.  
  5. 仮想ライブラリを作成したら、そのライブラリを右クリックして出てくるポップアップメニュー「Select」しなければならない。
  6.  
  7. 仮想ライブラリをSelectしたら、その後に必ず仮想ドライブ(こちらは仮想ライブラリを作成した時点で自動的に作成されている)も「Select」しなければならない。
  8.  
  9. 仮想ライブラリと仮想テープ装置をSelectし終わったら、必ずウインドウ左上のSaveボタンをクリックすること。

以上を理解するまで延べ2日(; ;) マニュアルから読み取りにくくてしょうがない。

頭の疲労が激しい気がする。

■複数マシンを対象としたバックアップ

今構築中のRHELサーバとは別に、Windows2003Serverもあったりして。このWindows2003サーバからRHEL上のデータをバックアップできるかどうか試してみる。

     
  1. まずはWindows2003サーバにNetVaultをインストール
      netvault710配下にあったWindows用のインストーラでインストールできた。こちらはアップデート扱いではないの?インストール後のリブート要求は無かった。
  2.  
  3. 他サーバが見えるかどうか確認
      NetVault GUIツールを起動。Client Managementウインドウを開いてみる。
      Client Managementウインドウ上にあるClientsタブの中にはリストボックスが二つ並んでいる。一つはClients。もう一つはAvailable NetVault Machines。自分自身(Windows2003サーバ)はClientsの方に表示されているが、RHELサーバの方はAvailable...の方に表示されている。
      RHELサーバを右クリックして出てくるポップアップメニューで「Add as Clients」をクリック。。。追加に失敗した旨のダイアログボックスが表示された。
    ここでマニュアルとかあさってみたりすると、nvconfiguratorの方で少し設定をしないと行けないらしいことが判明。
  4.  
  5. クライアントとして追加されることを許可する設定
    RHEL側でnvconfiguratorを起動する。Securityタブの中に、「This machine may be added as a client to a server.」というチェックボックスがある。ここをチェックしないと、他のサーバから自分のデータをバックアップさせることができないらしい。
  6.  
  7. 再挑戦
      上記設定後、もう一度Client Managementウインドウを開く。と、今度はRHELサーバをNetVaultクライアントとして追加することができた。

実際のバックアップはまた今度。その前にどこのサーバをNetVaultサーバにするか決めないと(‥;)

2006年5月30日 (火)

サーバの時間調整

サーバの時間調整といえばntp。構築中のRHELにもntpdがインストールされていた。

# chkconfig --list |grep ntp
ntpd            0:オフ  1:オフ  2:オフ  3:オフ  4:オフ  5:オフ  6:オフ
#

ただよくよくntpdについて調べてみると、今回のうちの構築では別にntpdじゃなくても良さげ。

ntpdは、他のntpサーバから正確な時間を取得して自分のマシンの時計を合わせる機能を持っている。ただそれ以外に、自分自身がntpサーバとなって正確な時間を配信するという機能も持っている。

正直、別にそこまでするつもりは無い。既にドメイン内には他のntpサーバが動いてるし。うちのサーバはとりあえず時間を合わせるだけでいい。ntp的にはクライアント機能だけで言い。

そういうときはntpdateコマンドというのを使うのが吉らしい。

使い方は、cronで定期的にntpdateコマンドを投入するだけ。デーモンが常駐するわけでもなくセキュリティホールにもならない。

ntpdateの使い方は、次の通り。

# ntpdate ntpサーバ名

このコマンドをcronを使って定期的に投入する。

cronの設定はユーザ毎のものと、システムで持つものと2種類あるらしいが、今回のはシステムでの設定にする。

どうやってやるかというと、/etc/crontabの編集をして登録する。RHELはインストール時から以下のような内容になっている。

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/

# run-parts
01 * * * * root run-parts /etc/cron.hourly
02 4 * * * root run-parts /etc/cron.daily
22 4 * * 0 root run-parts /etc/cron.weekly
42 4 1 * * root run-parts /etc/cron.monthly

これにntpdateコマンドを追加する。

記述フォーマットは次の通り。

分 時 日 月 曜日 ユーザ コマンド

実際に書き込んでみる。

48 3 * * 6 root /usr/sbin/ntpdate ntpサーバ名

この行の意味は、「毎週土曜日の午前3時48分にntpdateコマンドを実行する。」という意味。


ちなみに曜日は0~7を指定することができて、0と7は日曜日。

cronによって実行されたかどうかはログを確認。/var/log/cronを参照する。

先週の土曜日に動いたときのログ。時間が経過していたのでログはバックアップされておりファイル名はcron.1。

# cat cron.1 |grep ntp
May 27 03:48:00 redsox CROND[7507]: (root) CMD (/usr/sbin/ntpdate ntpサーバ名)
#

2006年5月24日 (水)

SAMBA 3.0の不思議

現在仕事場で新しいサーバを構築中。telnet、ftpd、httpdのセットアップはほぼ終了。あとはsambaとnamedとメールサーバのセットアップが残っている。

今日はsambaを突貫工事でセットアップした。

このサーバー、3人ほどのメンバーで構築していたところを、一人が職制異動になったために自分がその代わりに途中から参加している。他のメンバーから「sambaの構築は途中までやったけど、RHEL3に入っているsamba 3.0では日本語対応してないからダメっぽい」という話を聞かされた。

いったいどういう事なのかと調査開始。

日本sambaユーザ会のページを見ると、次のような情報を得ることができた。

Samba 3.0については日本語版という形でリリースする予定はなく、開発元である samba.orgでのリリースにマージしていく方向で各位努力しています。

samba 3.0は日本語対応しないってことなのか。

更に調べていくと、samba 3.0で日本語を使用するには次の二つの方法があるらしい。

  • glibcにパッチを当てる。
  • libconvにパッチを当てて、sambaをリコンパイルする。

glibcへのパッチが継続的にリリースされる保証は無いだろうし(もちろん今はこまめにリリースされてるだろうけど)、sambaリコンパイルの方は単純にRPMを使うだけでは済まなくなってしまうので面倒。

んー。難しい選択だね。

実際のところ、業務でsambaを使う場面でファイル名や共有名に日本語を使わなければいけない場面はないだろう(というか使っていてもやめさせるよう説得できる自信がある)と踏んで、3.0でセットアップしてみることにした。

とりあえず、smbがシステム起動時にスタートできるように設定。

chkconfig --level 35 smb on

あと、swatも起動するように、/etc/xinetd.d/swatで、以下を設定。

disable = no
only_from 自分のPCのIPアドレス

そして、各デーモンをその場でスタート。

service smb start
service xinetd restart

で、「http://サーバ名:901/」にアクセス。すると、swatの画面が文字化けしている。早速インターネット検索したところ、ここの文字化けはsmb.confの設定でなんとかなるらしい。

そこで、smb.confの内容に以下の設定を追加。

[global]
dos charset = CP932 unix charset = EUCJP-MS display charset = EUCJP-MS unix charset = UTF-8 display charset = UTF-8 ※2006.5.24 勘違いしてたので訂正

これで、swatはうまく動いた。

この後共有を作って、ユーザを作って、実際にWindowsからネットワーク経由で共有を設定してみると・・・・

うまくいくじゃないかw

日本語ファイル名、日本語フォルダー名も全く問題なく作れてしまう。




samba 3.0って日本語対応してないんじゃないのかヽ(`д´)ノ






RedHatがうまくなんとかしてくれてるってこと?(・_。)?(。_・)?

よーわからん。とりあえずうまくいったから良しとする。

※2006.5.24 追記

共有名に日本語を使うと、共有しようとしたときにエラーとなる。日本語対応とはこのことか?

新しいVAIO U

「いつも両手で握っていたい」秀逸の使い心地──ソニー「VAIO type U VGN-UX50」

この小ささでWindowsXPのHomeとかProとかが普通に入ってるらしい。

最近話題のOrigamiプロジェクトっていうかUMPCってやつは、確かWindowsXPのTablet PC Editionだったと思う。Tablet PC Editionがどんなものかはよく知らないけど、とにかく今回のVAIO type Uに入っているのは、亜種とかではなくて普通のWindowsXP。ある意味、UMPC以上ってことか?

まぁ普通にすげーって思いました。

こちらはSonyのVAIO Uのページ。

UX90という、ソニーのオーダーメードモデルのスペックをチェック。

CPUはCelron MとかCore Solo プロセッサーとかいうやつ。よく知らないけど。クロックはどれも1GHz程度。

HDDは30GByteと20GByteの二つから選択。

メモリーが512MByte。・・・これはちょっと少ない?

周辺機接続にはUSB2.0、ヘッドホン出力、マイク入力が各1個ずつ。内蔵無線LANとBluetoothも内蔵。

メモリースティックDuoやCFカードもさせるらしい。指紋センサーや、内蔵カメラなんてのもあるな・・・。

DVDドライブが別売りってのはやっぱり外付けなんだよな。この大きさじゃ内蔵無理だしw

TVチューナーの文字は見えない模様。

とにかく写真を見ると小ささがよくわかる。両手にすっぽり収まる感じというと言い過ぎか?自分にはそう見えた。

こういうのだと使ってみたいなぁと思うんだけど、いかんせん自分が使うシーンが無かったり。

こういうのを使いこなせる人にはなってみたいという憧れだけで、ボーナスつぎ込もうかどうか検討中w

2006年5月17日 (水)

Apacheで認証

構築中のWebサーバで認証の仕組みを導入する必要があるため、少し調べてみた。

ちなみに、今まで自分はApacheでこの辺の設定をしたことがない。まぁ趣味で立てるサーバでそこまでする必要が今までたまたま無かったというだけのこと。

ちなみに、自分のやりたい制限とは、、、

     
  • 特定のアドレスからは無条件に見せる
  •  
  • それ以外のアドレスからはID+パスワード入力で見せる
  •  
  • 上記二つを満たさない場合はアクセス不可とする

Apacheでは、特定のページやディレクトリをアクセスするときにユーザ名やパスワードを入力させたり、あるいは特定のIPアドレスからしかアクセスできないようにしたりすることができる。

ネット中をざーっと斜め読みしていると「.htaccess」ファイルを使用することが実現可能のようだ。

ところが、Apacheのドキュメントを見てみると.htaccessによる認証を推奨していない。それどころか、できるだけ避けろと言っている。「本来は主設定ファイル(httpd.confとか)の方で設定できるので必要ないはず」だそうである。

Apache 2.0のドキュメントに書かれている「.htaccess」ファイルを使ったときのデメリットは次の2点。

     
  • あるディレクトリにアクセスすると、カレントのディレクトリの.htaccessの他に上位のディレクトリにある.htaccessも探してアクセスルールを適用する。この動作のため、サーバの性能に影響する。
  •  
  • .htaccessはユーザがサーバの設定を変更する手段であり、管理者による一元管理を阻害する。

必要無ければ、基本的にはhttpd.confの<Directory>ディレクティブを使用して設定し、AllowOverride noneを指定して.htaccessで制御させないようにするのがいいということか。

【confファイルの作り方】

RHEL+Apache 2.0.46を前提に話を進める。

httpd.confの場所は「/etc/httpd/conf/」である。また、この他に「/etc/httpd/conf.d/」というディレクトリが存在し、その配下に*.confというファイルがいくつか存在している。

例えばperl.confとか、php.confとか。

これはhttpd.confの中に「Include conf.d/*.conf」という行があり、この設定でconf.d配下の設定をインクルードしている。

今回アクセス制御するためのconfファイルも、conf.d/配下に置くことにする。

【ホスト毎に制限する方法】

次のように記述することで、特定のIPアドレスからのアクセスのみを受け付けることができる。

1: <Directory アクセス制限ディレクトリのパス>
2:   Order Deny,Allow
3:   Deny from All
4:   Allow from 10.1.2.3
5:   Allow from 10.1
6:   Allow from 10.1.0.0/255.255.0.0
7:   Allow from 10.1.0.0/16
8:   Allow from apache.org
9: </Directory>
2行目:Order Deny,Allow

この行を書くことで、先にDenyディレクティブが評価されて、その後にAllowディレクティブが評価される。

3行目:Deny from All

全てのホストからのアクセスを拒否する。1行目で「Order Deny,Allow」としているので、まず全てのアクセスを拒否しておいて、その後許可するホストだけを記述していくというやり方。

4行目:Allow from 10.1.2.3

IPアドレスが「10.1.2.3」であるホストのアクセスのみを許可する。

5行目:Allow from 10.1

IPアドレスが「10.1.xx.xx」であるホストのアクセスのみを許可する。

6行目/7行目

ネットマスクを組み合わせたIPアドレスの指定方法。例では「10.1.xx.xx」が許可される。

8行目:Allow from apache.org

指定された文字列を含むドメインからのホストを許可する。

【ID+パスワードで制限する方法】

指定の方法は次の通り。

1: <Directory アクセス制限ディレクトリのパス>
2:   AuthType Basic
3:   AuthName "Secret Page"
4:   AuthUserFile パスワードファイル 5:   Require valid-user 6: </Directory>
2行目:AuthType Basic

認証方式を指定する。選択できるのは2種類で、「Basic」認証か「Digest」認証のどちらか。

Basic認証の方は対応しているブラウザが多く導入しやすいが、Digest認証に比べるとセキュリティ的に弱いらしい。パスワードファイルの作成方法によってはパスワードそのものを暗号化できるので、全く筒抜けというわけではないけど。

Digest認証はHTTP/1.1に対応したブラウザである必要がある。セション開設毎に値が変化するので、パケットを盗聴されても解読はかなり困難。

例えば、Netscapeの4.7くらいだとHTTP/1.1に対応してなかったような・・・・(不確実)

いまだにうちの会社でNetscape 4.7を使っている人がいる方が問題っちゃー問題なのだが。

3行目:AuthName "Secret Page"

Apache2.0ドキュメントを見ると、「認可領域の名前を指定する」とある。実際のところ、ブラウザ側のパスワード入力ダイアログのタイトルに、この文字列が表示されることが多いらしい。上記の場合は、「Secret Page」と表示される。

4行目:AuthUserFile パスワードファイル

パスワードファイルの場所を指定する。通常は絶対パス指定。パスワードファイルの作り方は後述。

5行目:Require valid-user

パスワードファイルに登録されているIDのうち、どのIDを認証させるかを指定できる。valid-userの場合は、登録されている全てのユーザ。

つまり、パスワードファイルに登録されていても、Requireディレクティブの指定によっては無条件にアクセスできなくなることもある、ということ。

【パスワードファイルの作り方】

htpasswdコマンドを使用する。Apacheがインストールされていれば、一緒に入っているはず。

ファイル生成(と同時に最初のID登録)
htpasswd -c  パスワードファイル名 登録するID
(2回目以降の)IDの登録
htpasswd パスワードファイル名 登録するID

デフォルトではパスワードをcryptでハッシュするんだそうで。MD5でパスワードをハッシュするときは「-m」もつけるとできるらしい。

【Satisfyディレクティブ】

普通にDirectoryディレクティブの中にIPアドレス制限とID制限を記述すると、二つとも認証をクリアしないとアクセスさせてくれない。

でも自分がやりたいのは、そのどちらかの認証をクリアできればいいという認証方法である。

これを実現するのが、Satsfyディレクティブである。

つまり、Directoryディレクティブの中にIPアドレス制限とID+パスワードの設定があった場合、「両方の認証を通らないとダメ」なのか、「どっちか片方の認証を通ればいい」のかを指定するのが、このSatisfyディレクティブということ。

自分のやりたいことを考えると、「Satisfy Any」を指定するといいらしい。デフォルトは「Satisfy All」なので、うまくいくはずがなかった。

【できあがり】

conf.d/配下にnewacess.conf(ファイル名は何でもいいんだけど)を作って、中身を以下のようにした。

1 : <Directory アクセス制限するディレクトリのパス>
2 :   Order Deny,Allow
3 :   Deny from All
4 :   Allow from 10.1
5 :   Allow from 10.2
6 :   AuthType Basic
7 :   AuthName "Secret Page"
8 :   AuthUserFile パスワードファイル 9 :   Require valid-user 10:   Satisfy Any 11: </Directory>

ちなみにIPアドレスは仮。

2006年5月15日 (月)

ファイルアップロードその後

 以下、PHP 5.1.2を使ってファイルのアップロードを勉強したときの成果。

★PHPでXHTMLを使うときの設定

XHTML+PHPを使用する場合、php.iniの中の「short_open_tag = Off」という設定をしなければいけないようだ。

Onにすると、文書の中のphpの部分を示すセパレータは「<? ~ ?>」となるが、これがXHTML文書の冒頭に必要なxmlの宣言「<?xml version="1.0" encoding="EUC-JP"?>」とかぶってしまう。実際、1行目でphpのエラーとなってしまった。

Offにすることで、文書の中のphpの部分を示すセパレータは「<?php ~ ?>」となるので、xml宣言とかぶる事が無くなる。

★PHPでXHTMLを使うときのid要素とname要素

ネットを見て回ってXHTMLに関する情報を斜め読みしていた。この中で、

XHTML 1.1においてはname要素は廃止され、id要素を代替として使用する。

みたいなことが書いてあった。

なるほどと思い、アップロードフォーム内のinputタグに書いてあったname要素をid要素に書き換えたら、期待通りの動作をしなくなってしまった。

PHPでアップロードファイル名を検出するためにはinputタグのname要素が必要だが、XHTML 1.1ではname要素は使えない。だからXHTMLとPHPを同時に使用することはできない・・・・というのが誤った判断だということがわかった。

実際のところは、XHTML 1.1でもinputタグ内のname要素は許されているらしい。

★アップロードのフォーム

<form action="phpファイル名" enctype="multipart/form-data"
    method="post">
<input type="file" name="sendfile"><br />
<input type="submit" value="アップロード">
</form>
     
  • ポイント1(formタグ)
      action要素にはアップロードしたファイルを処理するphpのファイル名を指定する。enctype要素とmethod要素については、上記サンプルの通りの値で固定と考えてもいいかもしれない。
  •  
  • ポイント2(inputタグ)
      上記サンプルではinputタグ(type="file"、またはtype="text")でファイル名を利用者に入力させている。この時、name要素を必ず指定する必要がある。PHPのスクリプト側で、このname要素を利用してファイル名を取り出すことになる。

★アップロードされたファイルの情報

まず、アップロードされたファイル自体は一時ファイルとしてPHPの一時ディレクトリに格納される。複数ファイル指定されていても、ちゃんと分割した状態で格納してくれる。これはPerlではやってくれないことなので、かなり楽に感じた。

要はスクリプトの内部に、送信されてきたデータを受け取って保存するという処理を書かなくてもいいと言うこと。

ファイルがアップロードされてくると、アップロードされたファイルの情報がPHPのスーパーグローバル配列「$_FILES」(スコープに関係なくどこからでもアクセス可能)にセットされる。セットされる情報と参照方法は次の通り。(name要素が"sendfile"の場合)

$_FILES['sendfile']['name'] 元のファイル名
$_FILES['sendfile']['type'] MIMEタイプ
$_FILES['sendfile']['size'] ファイルサイズ
$_FILES['sendfile']['tmp_name'] 一時ファイル名

これらの情報は、アップロードされたファイル毎にセットされる。

★アップロードされたファイルの実体

実際にアップロードされたファイルは、PHPの一時ディレクトリに保存される。が、PHPの動作を見ていると、アップロードの処理が終わってしまった後はアップロードされた一時ファイルを削除してしまうようである。

PHPスクリプト側では、ファイルがアップロードされたらすぐに(アップロードされたときのセションの中で)move_uploaded_file()関数を使って別の場所に移動させる必要がある。

move_uploaded_file()関数は、一つめの引数に一時ファイル名(これは$_FILESの一時ファイル名)、二つめの引数にファイルの移動先を指定する。

★サンプル

次の例は、フォームの表示とアップロード処理を一つのファイルで実現するためのPHPスクリプト。

フォームの表示時には、アップロードされているファイルの一覧も表示する。

postメソッドのリクエスト内に「func=upload」が含まれている場合のみ、アップロード処理を実行する。アップロード先は「.data/」ディレクトリ配下。

<?xml version="1.0" encoding="EUC-JP"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
      Transitional//EN" "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html;
        charset=EUC-JP" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<title>PHPによるファイルアップロードテスト</title>
</head>
<body>
<?php
//HTML本体表示
  print 'funcは'.$_POST{'func'}.'<br>';
  if($_POST{'func'}=='upload'){
    DoUpload();
  }
  else{
    Display();
  }
?>
</body>
</html>

<?php
//---------------------------------------------------------------
//--関数---------------------------------------------------------
//---------------------------------------------------------------
function Display()
{/*アップロードフォーム表示処理
*/
?>
<h2>ファイルアップロードテスト</h2>
<form action="upload.php" enctype="multipart/form-data"
             method="post">
<input type="file" name="sendfile"><br>
<input type="submit" value="アップロード">
<input type="hidden" name="func" value="upload">
</form>

<div style="text-align:center;">
<table border="1" style="width : 60%;">
  <tbody>
    <tr>
      <td>ファイル名</td>
    </tr>
<?php
  $h = opendir('./data');
  while(($file=readdir($h))!==false){
    if(($file!='.')&&($file!='..')){
      print '<tr><td><a href="./data/'.
           $file.'">'.$file.'</a></tr></td>'."\n";
    }
  }
?>
</tbody>
</table>
</div>

<?php
}
//---------------------------------------------------------------
function DoUpload()
{/*ファイルアップロード受付処理
*/

  if(strlen($_FILES['sendfile']['name'])==0){
    print 'ファイル名が入力されませんでした。<br>';
  }
  else{
    print 'ファイル名:'.$_FILES['sendfile']['name'].'<br>';
    print 'MIMEタイプ:'.$_FILES['sendfile']['type'].'<br>';
    print 'ファイルサイズ:'.$_FILES['sendfile']['size'].
          'バイト<br>';
    print '一時ファイル名:'.$_FILES['sendfile']['tmp_name'].
          '<br>';

    $result = move_uploaded_file(
                $_FILES['sendfile']['tmp_name'],
                './data/'.$_FILES['sendfile']['name']);
    print '<br>';
    if($result==FALSE){
      print 'ファイルのアップロードに失敗しました。<br>';
    }
    else{
      print 'ファイルのアップロードを完了しました。<br>';
      print '<a href="./upload.php">戻る</a>';
    }
  }
}
//---------------------------------------------------------------
?>

2006年5月 2日 (火)

今更Mozillaにハマったり

食わず嫌いのブラウザ選択

会社で利用しているPCでは、仕事上IEを使わないといけないことが多い。というか、社内のサイトにIE専用というのがたまにあって、そいつらのせいでIEを選択する機会が多い。

IT企業とか言っておきながら、社内のサイトの実情なんてこんなもんなんだ。

実際のところはIEエンジンを利用したブラウザであれば支障はほぼ出ないので、自分自身はLunascapeというのを使っている。

ところが、先日仕事上で必要な社内のとあるサイトにアクセスしたところ、ページ表示のエラーが出てしまった。飛び交っていた情報によると、

「先日のパッチの影響と思われる。.NET Framework 2.0をインストールしていると、ページを表示することができない。アンインストールするように。」

だそうだ。

.NET Framework 2.0なんて、まぁ使わんだろうしいいかと思いながらアンインストールを試みるも、ものの見事にアンインストールがハング。どうもうまくいかない。

「Firefoxなら見ることができる。」

え、そうなんだf(-_-;)

そんなわけで、早速インストールしてみた。Netscape 4.7を最後にしばらくおさらばしていたGeckoエンジンと、久しぶりの再会だ。

実際使ってみると、Lunascapeに比べてずいぶん動作が軽快だ。「こりゃぁいいや」と思いながら、拡張機能やらスキンやらをいじくり倒して、自分好みにカストマイズ仕事そっちのけw

ブックマーク形式に互換は無いが、最近はWikiシステム側にブックマークを移してしまったので無問題(´―`) 後輩から「マウスジェスチャー使えない」との指摘を受けたが、もともと自分は使ってないから、これも無問題w

やっぱ軽いのが一番^^

でもやっぱりIE専用のサイトの閲覧ができないところに引っかかるorz

しょうがないから、そこだけLunascapeを使って、普段はFirefoxを利用することにしました。なんでも使ってみるもんだね。何となくマイナスのイメージがあってGeckoエンジンもののブラウザを避けてたけど、これが食わず嫌いってもんだ。

Thunderbirds are GO!

Firefoxをインストールしたついでに、メールクライアントの方も同じくMozilla提供のThunderbirdをインストールしてみた。物が良ければ、こちらも移行してしまおうかという算段。

んー・・・使ってみるとどうもやっぱり機能的には秀丸メールの方が多彩だし、自分に必要な機能がThunderbirdには無かったり、、、、こっちは却下かなぁ。

と思っていたら、こっちは標準でRSSを読む機能がついてる。早速使ってみたらこれが大満足(^o^)

以前RSSリーダーの記事を書いたときに、「メーラーdeネットニュース」+「秀丸メール」という環境でしばらくブログを読んでいたが、それでもやはり一部動作に不満があったりしてた。

が、Thunderbirdにはそれが全く無い。ヤプログの記事タイトルもちゃんと取れるし、ITMediaの記事も二重に取ってくることも無いし。取ってきた記事の中にhtmlタグがあるとちゃんとタグに沿って表示してくれたり(この辺、それがいいのかという問題はありそうだけど)。

そんなわけで最近は、メーラーは相変わらず秀丸メールを使ってRSSリーダーにはThunderbirdを使い始めた。しばらくはこれで大丈夫っぽい。


Mozzila Japan
http://www.mozilla-japan.org/

2005年11月29日 (火)

やっとBCBがバージョンアップ?

 先日フラフラとネットサーフィンしていたところ、Borlandのページで新製品発売の文字を見つけた。その名も「Borland Developer Studio 2006」。詳細を見ていくと、中身はC++BuilderとDelphiとC#Builderらしい(!)

 しまった。これは買わないと。

 如何せん、Borlandに登録している我がアカウント情報は、もう何年前のだ? 当然バージョンアップのDMなんか、今の家に届くはずも無くorz

 というわけで、あわててボーランドのお問い合わせ窓口に電話した。また、気に食わないのは、ここ平日の10時から17時までしか受け付けしないとか。会社から電話するしかないじゃないか(--メ)

 でも応対そのものは大変親切ヽ(´―`)ノ

 登録住所が引っ越し2回前のやつだったのを現住所に変更して、二つ登録されていた自分のアカウント情報もまとめてもらい、メールアドレスも既に使えなくなっていたアドレスだったので変更して、電話番号も変更して、これでスッキリ(爽)。

 「今ですと、最新の製品としてはデータベーススタジオのご案内をお出ししm」デベロッパースタジオだよな?

な?

 オペレータのおネーチャンはよく分かってないらしい。


 とりあえず一通りお願いするだけして、はたと気がついた。もしかして、Studio2006に入ってるBCBって、まさかBCB6じゃ無いだろうな・・・まさかねぇ。っていうかバージョンはどうでもいいけど、新しいVCL使いたい。

 とっても心配なので、サイトの製品案内を見てみた。

Delphi、C++Builder、C#Builderを一つまとめた単一統合開発環境

 Delphiの最新VCLをBCBでも利用できるってことだ。どんな新しいのがあるかは知らんけど(´Д`;)でも、とりあえず嬉しい。

プラットフォームは目的に応じてWin32/.NETを選択可能

 これは後ろの方を読んでいくと、Win32版のDelphi、BCBコンパイラと、Microsoft.NET対応のDelphi、C#Builderが使えるらしい。まぁいいや。.NET開発はC#でやればいいやね。

ECO IIIによるラピッド開発

 これも.NET方面のみらしい。設計主導型開発とか書いてあるけど、よくわかんない。

Borland StarTeamとBorland CaliberRMを統合

 名前はたまに見るけど、何者かがわからない。

 説明上ではStarTeamが構成管理ツールで、CaliberRMが要件管理ツールなのだそうだ。これもまたよくわからない。

 こいつらが統合されることで、障害管理、変更管理、要件管理、UMLモデリング、バージョン管理、バグ追跡ができると書いてある。本当に意味不明。

 使いこなせるんだろうか・・・。

標準価格

     
  • Borland Developer Studio 2006 Architect日本語版 399,000円(税込)  
  • Borland Developer Studio 2006 Enterprise日本語版 336,000円(税込)  
  • Borland Developer Studio 2006 Professional日本語版 84,000円(税込)

 さ、さんじゅうまん・・・��(゜△゜;)

 迷わずPro買うしか無いわけね・・・orz

 バージョンアップ優待でどのくらい安くなるのか知らないけど。

2005年10月26日 (水)

CGIでファイルアップロード

 Web上のとあるページ(掲示板というか・・・1件ずつデータを入力していくページなのだが)に対して、今まではテキストかgifファイルしかアップロードを許さなかったものに、他の形式のファイルでもアップロードできるようにしなければならなくなった。例えばExcelとか、Wordとか、PDFファイルなんかも、である。

 そのページ、PerlによるCGIで実現されているのだが、そのスクリプトが持つ他の機能のこともあり、一から新しく作る気にはならなかったので、改造するほうがよさそうだと判断した。

 が。

 正直、アップロードの仕組みなんてよく知らなかったりする。

 以下はお勉強の成果。わかる人から見ればたいしたことは無いのだろう。ググればサンプルなんてゴロゴロでてくるし。

 だいだい次のようなことをすれば、HTTPでサーバにファイルを送り込むことができる。

  • アップロード用のフォームを含むHTMLファイルを作成する。CGIでもいいけど。
  • フォームからデータが送信されたときの受け取り用CGIプログラムを作成する。
  • 受け取る方は、複数ファイルを受信した場合はファイル毎にデータを分割するようにプログラムしておく。
  • 保存するときは必要に応じてコード変換などもできるといいかも。

アップロード用HTMLファイル

 次のようなHTMLファイル(または、次のようなHTMLを出力できるCGIプログラム)を作成して、Webサーバ上に配置する。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=EUC-JP">
<title>アップロードテスト</title>
</head>
<body>
<form method="POST" action="up.cgi" enctype="multipart/form-data">
ファイル名1<input size="20" type="file" name="file1"><br>
ファイル名2<input size="20" type="file" name="file2"><br>
ファイル名3<input size="20" type="file" name="file3"><br>
<input type="submit" name="btnUpload" value="アップロード">
<input type="reset" name="btnReset" value="リセット">
</form>
</body>
</html>

ポイント1:FORMタグ

 action要素に指定するスクリプトは、POSTメソッドでデータを受け取ることができるCGIプログラムを指定する。まぁ当たり前か。

 enctype要素にmultipart/form-dataを必ず指定する。理由は分からないが、これが大事らしい。

ポイント2:FORMの内容

 ファイル名入力用に、type要素にfileを指定したinputタグを記述しておく。複数ファイルを同時にアップロードする予定なら、inputタグも複数記述する。

 inputタグのname要素に、それぞれを識別可能な名前を記述すること。アップロード先でファイル名を取り出す際に使用する。

 あと、当たり前だが送信ボタン(type要素がsubmitのinputタグ)も必ず記述する必要がある。

アップロードCGIスクリプト

 POSTで送られてくるデータを受け取ってファイルに保存するスクリプトをperlで書いてみた。とりあえず、来たデータを全てファイルとして保存する。必要な部分を抜き出したり、ファイル毎に区切ったりとかは、後々やることにする。

#!/usr/bin/perl

#ファイル読込み
$len = $ENV{'CONTENT_LENGTH'};
binmode(STDIN);
read(STDIN,$data,$len);

#ファイル保存
open(OUT,"> test.dat");
binmode(OUT);
print OUT "$data";
close(OUT);

#ヘッダ出力
print "Content-type: text/html\n";
print "\n";
print "<html>\n";
print "<head>\n";
print "<title>ファイルアップロード</title>\n";
print "</head>\n";
print "<body>\n";

print "ファイル保存完了。<br>";
print "$data";

#フッタ出力
print "</body></html>\n";

ポイント1:POSTデータの読込み

 POSTメソッドが指定されている場合、アップロードされるファイルのデータは標準入力に入ってくる。なので、read関数で標準入力(STDIN)を読み込ませる。読み込む長さは、$ENV{'CONTENT_LENGTH'}で取ってくることができるので、必ずこの長さで。あと、文字コードが違うことやバイナリファイルが送られてくることを考えると、バイナリモードで読み込むのが吉らしい。

 他は特に目立った処理はしてないか・・・。

POSTメソッドでアップロードされるデータ

 前述のファイルを使って、実際にアップロードされるデータを見てみた。なお環境は、サーバ側がFedoraCore4 + Apache2、クライアント側がWindowsXP + IE6。

 アップロードするファイルの内容は次の通り。

1
2
3
4
5

 行の先頭に1、2、3、4、5と並べただけのファイル。各行は必ず行の終端で改行する。perlの文字列的に書くと「1\n2\n3\n4\n5\n」みたいな感じか。ちなみに改行コードはCR+LF。文字コードの方は半角文字ばかりでS-JISもEUCも無いが。ASCIIコード?

 で、こいつのファイル名を、先ほどのフォームの1番目と2番目のテキストボックスに入力して(つまり同じファイルを2度、サブミットボタンをクリック。実際にサーバー側に保存されるデータは次のような感じ。ファイル名のところは「*ファイル名*」としてあるが、実際には本当のパスが記述されている。

-----------------------------7d528020503f8
Content-Disposition: form-data; name="file1"; filename="*ファイル名*"
Content-Type: text/plain

1
2
3
4
5

-----------------------------7d528020503f8
Content-Disposition: form-data; name="file2"; filename="*ファイル名*"
Content-Type: text/plain

1
2
3
4
5

-----------------------------7d528020503f8
Content-Disposition: form-data; name="file3"; filename=""
Content-Type: application/octet-stream


-----------------------------7d528020503f8
Content-Disposition: form-data; name="btnUpload"

アップロード
-----------------------------7d528020503f8--

 このように、複数ファイルのアップロードをしても、一連のデータとして届いてしまう。ファイル毎に分割しようと思うなら、ここから有効なデータを抜き出す必要がある。

ポイント1

 各データの始まりは、ハイフンがいくつか続いた後に16進数らしき数字が並ぶ。この16進数文字列はデータのセパレータとして使えそう。

 そして、その行の最後は改行されているのだが、これがCR+LFとなっている(Windowsクライアントからのアップロードだからか?)。

ポイント2

 次の行には「Content-Disposition:」という行が続く。テキストボックスの場合は、「Content-Disposition: form-data;」まで固定で、その後「name=」や「filename=」等、各テキストボックス固有のデータが続く。指定されたファイル名も、ここを参照することで取り出せる。

ポイント3

 その次の行には、「Content-Type:」という行が続く。ただし、テキストボックスの場合だけ。ボタンの場合はこの行が無い。

ポイント4

 データが始まる前と終わった後に、必ず一回ずつ空行が入るらしい。

まとめ

  • データのセパレータは、データの先頭行の複数ハイフン+16進数文字列。
  • ファイル名は、セパレータで区切られた各データのうち、「\n\n」より前に出現する「filename=」。
  • ファイル内データは、セパレータで区切られた各データのうち、「\n\n」より後に出現するデータ。

 以上をperlのスクリプトとして実装するのは、また今度ということで。

2005年10月19日 (水)

FF11 LSメンバー登録システムのRSS配信

 今回は、一般公開されているCGIスクリプトをRSS配信できるように改造する話。とは言ってもβとして機能は入ってて少し動作がおかしかったので整えただけだけど。

 RSSとRDFに関する情報の出展はWikiPediaより。

RSSって何

 そもそも、自分がRSS配信のことをわかってない。RSSとはウェブサイトの更新情報を簡単にまとめて配信するためのフォーマットのこと。普通にRSSと言うと、次の3つの単語が当てはまるらしい。

     
  • RDF Site Summary  
  • Rich Site Summary  
  • Really Simple Syndication

 どれもRSSなんだそうだ。それぞれ記述方法も違えば、用途も違うらしい。

 ただ、日本国内でRSSというと、だいたいの場合はRDF Site Summaryの事を言うらしい。一番多く利用されているのはブログ。あとはニュース配信とか番組情報とか。

 そもそも最初のRSSは当時のネットスケープ社が作ったRSS 0.9で、これはRDF Site Summaryだった。

 その後、ネットスケープ社が独自のXMLフォーマットでRSS 0.91を作った。これはRDFを採用していない。こちらはRich Site Summaryと呼ぶ。

 その後RSS-DEVワーキンググループが作ったRSS 1.0がリリースされた。RDFが採用されているので、これはRDF Site Summary。

 更にその後、Userland Softwareというところから0.91系を拡張した0.92、0.93、0.94をリリース。こちらは0.91の拡張なので、Rich Site Summary。

 Userland Softwareは最終的に0.91から0.94の全てに互換を保証する形でRSS 2.0をリリース。これをReally Simple Syndicationと名付けた。

 このRSS 2.0に対する不満という形でIBMを中心としてRSSとは別のAtomという新しいコンテンツ配信技術が開発された。

RDFって何

 ウェブ上のリソースについて情報を記述する際に用いられる枠組みのこと。W3Cが企画の策定をしており、RDFの応用例としてRSSやFOAFなんてものもあるらしい。

FF11 LSメンバー登録CGIのRSS配信

 FF11プレイヤーで、LS用のサイトを持っているところなんかにはよく使われていると思う。LS(他のMMORPGなんかで言うところのギルド)のメンバー情報を登録するためのCGIスクリプトで、配布元は瀬野部屋.com

 念のため書いておくと、このスクリプトでのRSSフィード機能はβ機能であるとconfig画面には書いてある。記事自体はver0.53auをベースに書いているが、この先のバージョンでどうなるかはわからない。

 あと、下の記事でスクリプトに改造を入れている。一応動作確認して自分の要件は満足しているものの、この機能について動作保証するものではない。

 出力される配信ファイルの拡張子が.rdfなので、たぶんRSS 1.0なんだろうなきっと。

スクリプトの動作概要

 RSS機能を利用するのであれば、以下の設定を行なう必要があるようである。

     
  • マスターモードの画面で、「履歴取得」を「使用」にセットする。  
  • マスターモードの画面で、「RSSフィード設定(テスト用)」も「使用」にセットする。  
  • setup.cgi内の最後の方にある$site変数と$channelurl変数に正しいurlを設定する。

 $site変数と$channelurl変数の内容は、rdfファイル内の各所に利用される。実際、RSSは本来どう書けばいいのか調べてみたが諸説紛々というか、アレは間違ってるコレは正しいとか混乱しているようで自分にもよくわからなかったので、一応考えた末に次のようにした。

$site="http://webサーバ/guildmember/";
$channnelurl="http://webサーバ/guildmember/guildmember.cgi";

 スクリプト本体「guildmember.cgi」はドキュメントルート直下のguildmemberディレクトリに配置している。

 ソースを見ると、日記やコメントの書き込み/更新等のタイミングで履歴ファイルにデータを追加している。そして、その処理の延長で履歴ファイルを読込みし直して、それを元にrdfファイルを出力している。履歴ファイルにデータが追加される毎に、rdfを逐一最初から作り直すといった感じ。

コメントの時間が変

 実際にrssリーダで読んでみると、日記投稿やプロファイル更新の時なんかは普通に読めてるんだけど、日記に対するコメントの投稿の日付が変になってる。

 自分の環境は「メーラーdeネットニュース」+「秀丸メール」なのだが、81/11/24とかいう日付が入る。

 また他のrssリーダーでも、情報が配信された日付時間のところが空白になるので、正しくソートできない。

 で、実際にrdfファイルを眺めていると、通常の投稿ではそのアイテムの日付が記録されているのに対して、日記のコメントの時は空っぽになっているのが見えた(<dc:date>タグ)。これが原因か?

■日記の更新配信データ
<item rdf:about="★★★">
<title>★★★ さんの日記[xxxxxxxx]更新</title>
<link>http://dummy/</link>
<description>★★★</description>
<dc:date>2005-10-18T16:18:00+09:00</dc:date>
</item>
■日記に対するコメントの配信データ
<item rdf:about="★★★">
<title>★★★ さんの日記[xxxxxxxxxx]に★★★さんがレス</title>
<link>http://dummy/</link>
<description>★★★</description>
<dc:date></dc:date>  ←ここに日付データが入らない。 </item>

 ソースファイル上、rdfファイルを作成している部分ではどのようになっているのかを確認してみる。検索キーワード「<dc:date>」で検索してみると、「rss_write」というサブルーチンが見つかった。

 サブルーチンの内容を確認したが、該当の日付出力部分については常に$rtime変数の内容を出力しているだけ。

$item .="<item rdf:about=\"$rsource\">\n<title>$rstitle</title>\n
<link>$link</link>\n <description>$rtitle $rbody</description>\n
<dc:date>$rtime</dc:date>\n</item>\n";
※ブログの記事の幅の関係上、適当に改行を入れた。

 この後、この$item変数をrdfファイルに出力している。

 じゃぁこの$rtimeの内容は何か。同じサブルーチンの先頭部分で読み込んでいる履歴ファイル内の各レコードをタブ区切りでsplitして、4番目の項目を$rtimeにしているのが見えた。

※@listには履歴ファイルの内容が全て読み込まれている。
foreach $i(@list){
($dummy,$rname,$rtitle,$rtime,$rmaxno,$rbody)=split(/\t/,$i);
(中略)
}

 ってことはこの処理でのファイルの読込み元である履歴ファイルに、既に時間が記録されていないということか。

 履歴ファイルの内容を見ると、確かにRESDIAレコード(たぶん日記のコメントレコード)については日付が出力されていないことが確認できた。

 ソース内では、履歴データを書き込むときは「必ず」$historybaseという変数に書き出すレコード内容を直前に作り込んでから、「add_history」サブルーチンを呼び出している。この文字列は、そのとき行なわれた新規/更新等を表す履歴データとなっている。そして、4番目の項目として$rsstimeという変数の内容を必ず設定している。

 履歴ファイル内にこの$rsstimeの部分が出力されていないと言うことは、履歴へデータを出力する「add_history」サブルーチンに渡る直前の$historybase作成時点で、$rsstimeに値が入っていないということになる。しかも日記のコメントの時だけ。

 ソース内を検索キーワード「$rsstime」で検索してみると、$rsstime変数に値を設定しているところは1カ所しか無く、「set_cookie」サブルーチン内だった。

 では、サブルーチン「set_cookie」はどこで呼び出されているのか。

 ソースの割と先頭の部分。160行目あたりから、if文とelsif文が連続してるところがある。$function変数の内容によって、呼び出すサブルーチンを変えている部分だ。識別子から推測するに、恐らくプロフィール更新、メンバー新規登録、日記投稿、日記更新、コメント投稿等であると思われる。

 プロフィール更新、メンバー新規登録、日記投稿、日記更新に関しては、「set_cookie」サブルーチンを呼び出した後に該当のサブルーチンを呼び出している。

 ところが、日記のコメントに関してはset_cookieではなくてget_cookieを呼び出している。他に各メンバーのプロフィール表示を行なう場合も、get_cookieを呼び出していることは呼び出しているんだが・・・

 get_cookieをset_cookieに変えてみたり、set_cookieしてからget_cookieしてみたりもしたが、一部動作不良があったので不採用。つーことは、このコメントのところに時刻処理を作り込むしか無いのだろうか・・・。

改造内容

 履歴ファイル上に対して、日記のコメントをした際に出力される履歴データにも正しく日付時刻を出力するように改造する。

改造方法と改造箇所

 コメントデータを投稿する際に呼び出されるres_diaryサブルーチン内で、履歴出力を行なっている部分(add_hitoryサブルーチン呼び出し直前)がある。この処理の直前に次の処理を追加する。手直しも少ないし、この方法がいいんじゃ無かろうか。

$rsstime="20$year-$mon-$mday"."T$hour:$min:00+09:00";

 そもそもコメント投稿でRSS配信するものなのかどうなのかも自分はわからなかったりする。よく見るブログじゃ、コメントのRSS配信なんて見ないし。このFC2にはあるみたいだけど。

 ただ、自分としてはコメントも記事の一つとして読みたいので、RSS配信したら便利かとは思ったりするんだよなぁ・・・。

2005年10月 4日 (火)

RSSリーダー模索

 なんとなくどんどんプログラミングから離れていく気がするが、気のせいと言うことにしておく。

 結構暇に任せてブログ巡りをしては読みあさったりしている。FF11をプレイしていた頃の名残なので、ほとんどはFF11プレイ日記のブログ様が多かったりはするけれど。

 気に入ったブログをブラウザのお気に入りにばんばん突っ込んでたら、なんかえらい数になって収拾がつかなくなったことが過去にあった。そこでRSSリーダーというものを使い始めたのだが、「これ!」と思うような物がまたなかなか無かったりする。とりあえず使い込んだのはMiech(なんか配布元urlが404エラーになるなぁ・・・)というやつと、glucose(配布元)というやつ。

 で、最近気がついたのが、普段自分が使っているメーラーである「秀丸メール(配布元)」で、どうもRSSを読み込んでくれるらしい? と、詳細を調べてみると、「メーラーdeネットニュース(配布元)」というソフトと組み合わせることによって秀丸メールでRSSリーダーっぽいことができるらしい。

 そんなわけで早速メーラーdeネットニュースと専用のRSSプラグインをダウンロードして、インストールしてみた。

 仕組みとしては、メーラーdeネットニュースを常時起動しておいてRSS情報をかき集めてきて、秀丸メールはメーラーdeネットニュースに対してpop3を使って(ローカルに)記事を取りに行くというしかけ。1記事1メールの形で。

 ちなみにメーラーdeネットニュースはシェアウェアだが、秀丸メールからのアクセスに限っては送金無しで利用可能である。

 メーラーdeネットニュースの方の設定をしてしまえば、あとの操作に関しては秀丸メールそのものだから、全く違和感なく操作できる。

 実際記事を取り込んでみると、ヤプログのブログで記事のタイトルが空だったりブログのタイトルに変な制御文字列らしきものが入ってたりする。本文は取ってこれてるのでいいけど。

 その他、@nifty(ココログ)、Excite(Exciteブログ)に関しては普通に取り込むことができた。

 また、ITMedia+DというニュースサイトのRSSも拾ってみたが、なんかこっちの方は同じ記事を何度も何度も取り込みやがる。秀丸メール上、相違点が見つからなくて単純に何度も同じ記事を取り込んでいるようにしか見えない。なんだかなぁ。まぁ秀丸メールの方で重複メール消す機能あるからいいけど。

 しばらくはこの組み合わせで行ってみようと思う。

2005年9月30日 (金)

XOOPSのインストールをやってみた

事の始まり

 前回の記事の冒頭に書いた「ある別のツールを使いたくて」いじりはじめたMySQL。使いたかったのは「Xoops」というソフトだ。

 別に目的があってという訳では無かったのだが、PHPの勉強をしていたときに「サイト構築の便利ツールがある」というのを小耳に挟んだことがあったのが、このツール。こういうのをコンテンツマネージメントシステム(CMS)と言うんだろうか?

 で、最近になって試しに使ってみようと公式配布サイトからダウンロードしてドキュメント見てみたら、なんかApachとPHPとMySQLが必要だとか書いてあった。で、前回の記事の冒頭に話が戻って、MySQLと格闘する羽目に合ったという次第。

MySQLセットアップ

 MySQLインストール直後にやっておかなくてはいけない導入オペレーション(らしい)。以下MySQLでのオペレーションが続くが、正直自分自身は理解しきれずにコマンドを打っている。

MySQLのrootのパスワード設定

 MySQLインストール直後のデフォルトとして、MySQLのrootユーザはパスワード無しとなっている。

$ mysql -u root    ←パスワード無しのrootで接続
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5 to server version: 4.1.11

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

 こんな感じで、パスワード無しでもrootが利用可能である。まぁこれはセキュリティ的にアレなので、これにパスワードを設定する。

$ mysqladmin -u root password 新しいパスワード

 これで新しいパスワードが設定された。試しに、

$ mysql -u root -p
Enter password: ******
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 7 to server version: 4.1.11

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

 ちゃんとパスワード付きで接続できる。

$ mysql -u root -p
Enter password: **    ←でたらめなパスワードを入力
ERROR 1045 (28000): Access denied for user 'root'@'localhost'
  (using password: YES)

 パスワードが正しくないとちゃんと拒否もしてくれる。

余計なユーザの削除

 まずはユーザ一覧を表示する。

$ mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9 to server version: 4.1.11

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> select user,host,password  from mysql.user;
+------+-----------------------+------------------+
| user | host                  | password         |
+------+-----------------------+------------------+
| root | localhost             | 0dfe8b7611822989 |
| root | localhost.localdomain |                  |
|      | localhost.localdomain |                  |
|      | localhost             |                  |
+------+-----------------------+------------------+
4 rows in set (0.00 sec)

 先ほどのオペレーションでユーザrootに対してはパスワードを設定したが、その他にパスワードが設定されていないユーザがいくつか存在している。ユーザ名が無いものまで存在している。無記名ユーザ?匿名ユーザ?よくわからないが、とりあえず先ほどパスワードを設定したユーザ以外は全部削除しよう。

mysql> delete from mysql.user where password='';
Query OK, 3 rows affected (0.03 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

mysql> select user,host,password from mysql.user;
+------+-----------+------------------+
| user | host      | password         |
+------+-----------+------------------+
| root | localhost | 0dfe8b7611822989 |
+------+-----------+------------------+
1 row in set (0.00 sec)

 これで余計なユーザは削除できた。

Xoopsインストール

 以降はXoopsのインストールを意識した作業になる。

Xoops用データベースの作成

 Xoopsが使用するデータベースを、インストール前にあらかじめ作っておく。

$ mysql -u root -p
Enter password: ******
mysql> create database xoopsdb;
Query OK, 1 row affected (0.00 sec)

mysql> show databases;
+----------+
| Database |
+----------+
| mysql    |
| test     |
| xoopsdb  |
+----------+
3 rows in set (0.00 sec)

 「xoopsdb」という名前のデータベースを作成できた。

MySQLにXoops用ユーザ作成

 XoopsがMySQLデータベースを利用するときのユーザを用意する。

mysql> grant all on xoopsdb.* to xoops@localhost 
 identified by 'xoopspasswd';
Query OK, 0 rows affected (0.00 sec)

mysql> select user,host,password from mysql.user;
+-------+-----------+------------------+
| user  | host      | password         |
+-------+-----------+------------------+
| root  | localhost | 0dfe8b7611822989 |
| xoops | localhost | 1cfb29116d6c1e74 |
+-------+-----------+------------------+
2 rows in set (0.00 sec)

作ったデータベースのテスト

 試しに、作ったデータベースでテーブルを作ってみる。

mysql> create table test ( test_field text );
Query OK, 0 rows affected (0.01 sec)

mysql> show tables;
+-------------------+
| Tables_in_xoopsdb |
+-------------------+
| test              |
+-------------------+
1 row in set (0.00 sec)

 うまくいってるっぽい。

 テストが終わったら削除する。

mysql> drop table test;
Query OK, 0 rows affected (0.00 sec)

mysql> show tables;
Empty set (0.00 sec)

Xoopsアーカイブ展開

 公式サイトから入手したXoopsのアーカイブをFTPでサーバに転送する。で、転送したらその場で解凍する。

tar xvzf xoops-2.0.12-JP.tar.gz

 展開されたファイルのうち、HTMLディレクトリ配下のファイルを全てWeb経由でアクセス可能な適当なディレクトリに移動する。

 ※chmodの作業は一切していない。していなくてもできるらしい。

Xoopsインストールウィザード

 Xoopsファイルを展開したディレクトリにアクセスすると、自動的にXoopsインストールウィザードが始まる。

 例えばサーバ名を「servername」、展開したディレクトリのドキュメントルートからのパスを「/xoops」とすると、「http://servername/xoops/」にアクセスするだけで、インストールウィザードが開始される。

 以後、基本的にはウィザードで表示されるメッセージの通りに進めていっただけ。悩むところは全く無かった。

 ・・・それにしてもなんかすごいな。urlのパスのチェックとかDBの接続チェックとかいろいろやってるらしい。アクセス権のチェックもしてくれるみたい。

 アクセス権のチェックのところでは、次のようなメッセージが表示された。

ディレクトリuploads/は、書込不可となっています。
 chmod 777してください。
ディレクトリcache/は、書込不可となっています。
 chmod 777してください。
ディレクトリtemplates_c/は、書込不可となっています。
 chmod 777してください。
ファイルmainfile.phpは、書込不可となっています。
 chmod 666してください。

 対処まではしてくれないわけねw まぁそれはそれで、素直に言われたとおりchmodした。

■インストール後

 さっそく「http://servername/xoops/」にアクセスして、自分のIDでログインしてみた。と、管理画面のところで、こんな早速メッセージが・・・。

注意:ファイル/home/user/public_html/xoops/install/が
 サーバ上に存在します。インストール完了後は必ず削除してくだ
 さい。

注意:ファイル/home/user/public_html/xoops/mainfile.phpへの
 書き込みが可能となっています。このファイルのパーミッション
 設定を変更してください。

 ふむ。早速対処しておこう。

rm -rf install/
chmod 755 mainfile.php

現在構築作業中・・・

 Xoopsに関してネット上見てると、カンタンカンタンという文字がよく目に入る。実際管理画面にはいるといろいろメニューも揃ってて、数クリックで設定できてしまったりする項目が多い。見た目も小綺麗だし。

 ただ、自分がやりたいと思うことを探すのに少し手間取っている。というかかなり手間取っている。

 むー・・・・この後はまた別の記事にするか・・・。

2005年9月28日 (水)

だからデータベースは嫌いだって言ったんだ。

MySQLをいじらざるを得ない状況

 最近Linuxサーバいじりの話しか書いてない。プログラミングはどこへ行ったのやら。



 ある別のツールを使いたくて調べ物をしているうちに、どうもMySQLを使えるようにしなくてはいけないらしいということがわかった。

 正直データベースと聞くと身の毛がよだつくらい嫌いで、ずーっと今まで避けて通ってきた道ではある。データベースのデの字も知らないと言っていい。食わず嫌い?んー・・・一応ほんの少しだけなめてみるくらいはしてるはず。

 まぁそんなことを言っててもしょうがないのでMySQLのセットアップをしてみることにした。

 ただ、Linux(FedoraCore4)をフルインストールしてしまっているため、インストール作業自体は完了しているし、とりあえずMySQLデーモンも起動できるということは、環境設定もある程度は完了していると言うところか。

ところが

 MySQLをいろいろいじっているうちに、何をしても工ラーが出るようになってしまった。例えば、

$ mysql -u root
ERROR 1045 (28000): Access denied for user 'root'@'localhost'
 (using password: NO)

 他にもmysqladminとかやったけど、現象的には同じ。

 localhostからのユーザrootのアクセスは許可されていない設定らしい。いろいろ調べてみると、インストール直後に関してはlocalhostからのrootユーザが接続して、rootのパスワードを設定(インストール直後はノーパスワード)したりするらしいが、これでは全く操作することができない。

 いじっていると、かろうじてmysqlshowコマンドでデータベースの一覧を出すことができた。結果はこうなる。

$ /usr/bin/mysqlshow
+-----------+
| Databases |
+-----------+
| test      |
+-----------+

 ネット上の調べでは、どうもtestというデータベースの他にmysqlというデータベースがあるのが普通らしい。また、このmysqlデータベースにはMySQLの権限テーブルが入っているらしい。

 権限テーブルというのは、どこからどのユーザがどういうパスワードで接続してくるのを許可する一覧のテーブル(あくまでも私の認識)。

 なるほど、接続できないのはこれが原因かと推測してみる。

 まぁ・・・いろいろと訳も分からないままいじくり回したので、どこかで消してしまったんだと思う。

権限テーブルのリセット

 調べてみると、mysql_install_dbというスクリプトがある。元来このスクリプト自体は、インストール直後の最初の一回だけMySQL権限テーブルを生成するものである。が、既に権限テーブルが存在している場合は何もしないらしい。

 mysqlデータベースが無くなっているんだから、こいつを使えばまた作れそうと思ったのだが、どういう訳かtestデータベースだけが残っている状態で何度もこのスクリプトを起動しても、一向にmysqlデータベースを作成する様子は無かった。

2.4.1. mysql_install_db の実行に関する問題

 ここには、mysql_install_dbについて記述されている。既に権限テーブルをインストールされている状態でmysql_install_dbするには、

# mv data-directory/mysql data-directory/mysql-old
# mysql_install_db
※data-directory/とdata-directory/は、my.cnfの中に設定されて
 いるパス名。

 としなければいけないらしい。要は、データディレクトリとして指定されている場所にある「mysql」というディレクトリを丸ごと削除(この場合は名前を変更して対処しているが)しなければいけないと言うこと。

 どうにかこうにかroot(ノーパスワード)で接続できた。ここがやっとスタートラインかorz

2005年9月20日 (火)

FedoraCore4インストールメモ

 なんてぇありきたりなタイトルだ。でも他に思いつかない。

 何度かFedoraCore4のインストールを繰り返していて、インストール直後あたりの手順が自分の頭の中で固まってきたので、まとめてメモにしておこうと思う。

 調べれば、同じような記事はネット上にゴロゴロしてはいるけどね。

やることは、

     
  • システムのデフォルト文字コードの変更  
  • Telnetを利用できるようにする  
  • FTPを利用できるようにする  
  • VNCを利用できるようにする

インストール

 この先何があるかわかんないので、「全てをインストールする」を選択。その他はデフォルト、またはインストーラお任せ。全部で6GBちょっとかな。そんな状況を前提として話を進める。

文字コード変更

 FedoraCoreでは文字コードにunicodeを採用している。このままだと少し影響が出るところがあり、自分としては以前のEUCのままの方がいいので、これを変更する。

 まぁなんせTeraTermがunicode対応してないもので・・・

ファイル名「/etc/sysconfig/i18n」の編集

 まずはログインしたあとに、suでユーザをrootに切り替える。そして、「/etc/sysconfig/i18n」というファイルをviで開く。

「i18n」の変更箇所

 ファイル内容は微妙にうろ覚えだが。ポイントは「UTF-8」を「eucJP」に変更するということ。

【変更前】
LANG="ja_JP.UTF-8"
SYSFONT="latarcyrheb-sun16"
SUPPORTED="ja_JP.UTF-8:ja_JP:ja"
【変更後】
LANG="ja_JP.eucJP"
SYSFONT="latarcyrheb-sun16"
SUPPORTED="ja_JP.eucJP:ja_JP:ja"

ファイル名「/etc/man.config」の編集

 続いて、「/etc/man.config」というファイルをviで開く。

「man.config」の変更箇所

 「PAGER」で始まる行がファイルの途中にある。

【変更前】
PAGER           /usr/bin/less -is
【変更後】
PAGER           /usr/bin/lv

Telnet

 世の中telnetよかsshでしょうとか言ってはいるが、あいにく自分がsshをよく知らない。勉強もしようとしてない。いつかはするかも。リスクは背負う覚悟で。

 そんなことよりTelnetで接続できるように準備する。

念のためインストールされていることを確認

 次のコマンドで確認することが可能。

# rpm -qa | grep telnet

 この結果、何も表示されずにプロンプトに戻ったのなら、telnetがインストールされていないことを疑った方がいいかもしれない。

ファイル名「/etc/xinetd.d/telnet」の編集

 続いて、「/etc/xinetd.d/telnet」というファイルをviで開く。

「telnet」の変更箇所

【変更前】
service telnet
{
        flags           = REUSE
        socket_type     = stream
        wait            = no
        user            = root
        server          = /usr/sbin/in.telnetd
        log_on_failure  += USERID
        disable         = yes }
【変更後】
service telnet
{
        flags           = REUSE
        socket_type     = stream
        wait            = no
        user            = root
        server          = /usr/sbin/in.telnetd
        log_on_failure  += USERID
        disable         = no }

xinetd再起動

 次のコマンドを投入して、xinetdを再起動する。

# /sbin/service xinetd restart

 これでxinetdが再起動してtelnetデーモンも立ち上がるはずなんだが、うまく行かないので結局リブートした。

FTP

 FedoraCoreでは、FTPとしてvsftpが付いてくる。ので、こいつをセットアップしてやる。Very Secureのvsだそうで。

ファイル名「/etc/vsftpd/vsftpd.conf」の編集

 ファイル名「/etc/vsftpd/vsftpd.conf」というファイルをviで開く。

「vsftpd.conf」の変更内容

 まず、利用者を限定して使いたいので、anonymousは禁止する。

【変更前】anonymous_enable=YES
【変更後】anonymous_enable=NO

 ASCIIモードでアップロードダウンロードできるようにする。

【変更前】
#ascii_upload_enable=YES
#ascii_download_enable=YES
【変更後】
ascii_upload_enable=YES
ascii_download_enable=YES

 とりあえず必要最低限はこんなところ?

システム起動時のデーモン起動設定

 次のコマンドを投入する。

# /sbin/chkconfig vsftpd on

FTPデーモン起動

 次のコマンドでデーモンを起動する。

# /sbin/service vsftpd start

VNC

 一応何かあったときのために使えるようにしておいた方が便利かなぁ〜・・・・たぶん。

ユーザアカウントでログイン

 この作業ではroot権限は必要無い。suしてる場合はexitしてしまう。

VNCサーバ起動

 次のコマンドを投入して、VNCサーバを起動する。

$ vncserver

 初回起動の場合、パスワードを求められる。このパスワードは、クライアント側から接続するときに必要となるパスワードをここで設定しろって言う意味。2回目以降のサーバ起動では、これを聞かれることは無い。

試しにクライアントから接続

 この時点で一度クライアントから接続してみる。うちの場合はWindows用のVNCクライアントを使用。接続先サーバー名には「サーバのIPアドレス:1」を指定(この「:1」はポイント)。

 とりあえず接続できるんだが、FedoraCoreのいつものデスクトップにはほど遠い画面が現れる。

 ここは一端VNCクライアントを終了しておく。

VNCサーバ停止

 VNCサーバも一旦停止する。コマンドは次の通り。

$ vncserver -kill :1

デスクトップをローカルと同じようにする設定

 ホームディレクトリに「.vnc」というディレクトリがある。その下に「xstartup」というファイルがあるので、まずはそれをviで開く。

「xstartup」の変更箇所

 これの設定について、ネットで調べてると諸説紛々といった感じだが、自分のところでは次の変更でとりあえず表示できた。

【変更前】
# Uncomment the following two lines for normal desktop:
# unset SESSION_MANAGER
# exec /etc/X11/xinit/xinitrc
【変更後】
# Uncomment the following two lines for normal desktop:
unset SESSION_MANAGER
exec /etc/X11/xinit/xinitrc

 もう一カ所。

【変更前】
twm &
【変更後】
#twm &
exec gnome-session &

とりあえずここまで

 結局この作業を3回やったのかな。ここまでなら手早くできるようになったw

 ・・・・自慢にもならねぇ(‥;)

 今は、apache2設定中。perlとphpが動けば、一気に現行サーバからコンテンツ移転予定。

2005年9月19日 (月)

FedoraCore4サーバ構築はじめました

 かなり以前から自宅サーバのソフト入れ替えをしたくてたまらなかったりしていた。

 ちなみに今のサーバはVine Linuxの2.6を使っている。Vineの最新版は、いまや3.2(3.2は開発版?安定版は3.1になるのかな)である。今のサーバを構築したのは去年の6月くらいだったかなぁ・・・。当時は最新版だったはずなのに、たった1年でずいぶん古くさくなるものだ。

 内輪向けのWebサーバではあるけれど、一応利用者がいる。移行する上ではできるだけ運用を止めないで移行したいと考えていた。apt-getとかをうまく使えば運用したままカーネルのバージョンアップまでできるらしい(未確認)が、残念ながら自分にはそこまで知識と技術と勇気は無かったりする。

 だから、やるならマシンをもう1台用意して、古い方を運用したまま新しいサーバを構築して、できあがったら一気にばさっと切り替えるような方法を考えていた。

 ところが。

 先日、現在運用中のサーバの設定をいじくり倒していたら、とうとうApacheが起動しなくなってしまった。httpd.confにエラーがあって、起動しない。

 httpd.confをどうにかすれば起動できるんだろうが、まずバックアップを取っていなかったというおバカぶりに加えて、どういう拡張モジュールがインストールされて有効になってるか無効になってるかなんて、ほとんど把握してないというていたらくぶり。

 Apache以外のサーバソフトは動いていたので、あわててWebデータをバックアップしてクライアントに退避。

 とりあえずWebminでApacheをいじるのは金輪際やめようと心に誓った。・・・・あ、あくまでも原因は自分の知識と技術の不足なので、念のため。

 「いい機会だし、新しいマシン用意してちゃんと作るか。」ということで、横浜のソフマップに出向いて中古PCあさりをしてきた。

 今自宅にいないので型番は確認できないが、FMVのビジネスユースモデルで、CPUが2.4GHzのP4、メモリ256MB、HDDが40GB、ディスプレイ無し、その他外箱、付属マニュアル、マウスなどが欠品(かろうじてキーボードは付いてた)という品物を、4万円弱で購入。性能的にはWindowsマシンとしては貧弱?かもしれないが、Linuxサーバならこれで充分だろうとたかをくくってみた。前のマシンと比較すると、CPUが800MhzのCelron?だった以外は、ほぼ同じスペックである。

 で、昨日からそいつにFedoraCore4をインストール中。

 よく考えてみれば、古い方のサーバが動かないのなら並行運用できるわけもなく、新しいマシン買うことも無かったことに、後で気がついた。はぁ・・・つくづくおバカかも。

 まぁでも、CPU早くなってるし、HDDのI/O速度も速くなってそう(勝手な想像)なので、サーバの反応速度も少しは改善されるかなぁなどと期待している。

 FedoraCore4にすると、一緒に付いてくるアプリもずいぶんとバージョンアップしているらしい。Apacheは2.0系になってるし、PHPは5が入ってるし。その他はよくわかんないけど。

 この辺の話もブログのネタになるんかな。いつか書くかも。

Premature end of script headers

 前回のFF11メンバーリストアップデートの時の話なのだが、実はこんな問題にもブチあたっていた。

現象発生

 自宅でやる前に、別の場所にあるサーバー(このサーバーも自分の自由にできる)で試してみた。このサーバー上に新CGIを展開、パーミッション設定して、いざブラウザでアクセスすると

「Internal Server Error」

 確かにこのサーバーではPHPばっかり動かしててCGI動かすのは初めてだったような・・・。なので、まず簡単なCGIプログラムで動くかどうか確認してみる。

 そのときのソースは以下。telnetでログインして、viで作ったperlのスクリプトである。

【ファイル名:test.cgi】
#!/usr/bin/perl
print "Content-type: text/html\n\n";
print "<html><head><title>test</title></head>\n";
print "<body>hello!<br>";
print "</body></html>";

 で、今度はこのCGIにブラウザからアクセスすると・・・。

「Internal Server Error」

 おかしい。同じエラーだ・・・。

 じゃぁ、コマンドラインから起動してみるか。

【コマンドラインからの実行結果】
$ ./test.cgi
Content-type: text/html

<html><head><title>test</title></head>
<body>hello!<br></body></html>

 コマンドラインから起動するとうまく行く。

ログを参照

 apacheが出力するエラーログを見てみると、次のようなのが残っていた。

Premature end of script headers: test.cgi

 ふむ。これが原因なわけね。

原因調査

 で、このメッセージをググってみると、結構いっぱい引っかかった。まとめてみると次のようなものが原因となることが多いらしい。

スクリプトの改行コードの問題

 スクリプトファイルでの改行をcr+lfにすると、こんなエラーになることがあるらしい。で、解決方法としては、ファイルの先頭の「#!/usr/bin/perl」を、「 #!/usr/bin/perl --」にすると解決できるとのこと。

 viで作ったしなぁ・・・あり得ないはずだよなとか思いながらも上記対処を試してみた。が、現象変わらず。

スクリプトのインタプリタのパスを誤って指定

 「#! /usr/bin/perl」が実は間違っていて、本当は「#! /usr/local/bin/perl」だったりするとこのエラーになるらしい。

 っていうか、コマンドラインで「./test.cgi」で動いてるから、これも間違いは無いと思っていいだろう。

CGIのパーミッションがおかしい

 cgiのパーミッションが間違っていた場合にこのエラーになるらしい。この場合644などから755に変更することで対処できる。パーミッションを確認してみると、このファイルに関しては「777」が設定されているので、まぁ問題はないでしょう。

「Content-type: text/html\n\n」を出力していない

 CGIの場合、出力の先頭に必ず「Content-type: text/html\n\n」を入れておかないといけない。それが無い場合にこのエラーになるらしい。

 実行結果リスト上は一応出てるんだが・・・綴り間違い?一応確認したけど、間違ってはいなさそう。

っていうか自分だけ?

 同じサーバーを利用している別ユーザーに聞いてみると、そちらではFF11スクリプトが正常に動作しているとのこと。

 はぁ?

 じゃぁ今度はその別のユーザのディレクトリ(別ユーザ配下のpublic_html/)をお借りして、そこに上記で作ったtest.cgiをコピーしてchmod +xして、ブラウザ経由でアクセスしてみると、

hello!

 あ、うまく行きやがった。

 わからん。

エラーログ

 ここでもう一度apacheのログディレクトリの中を漁ってみると、「suexec.log」というログファイルの中に、以下のログを発見した。

[yyyy-mm-dd hh:mm:ss]:
   uid: (500/uuuuuu) gid: (500/500) cmd: test.cgi
[yyyy-mm-dd hh:mm:ss]:
   file is writable by others:
    (/home/uuuuuu/public_html/test.cgi)
※日付、ユーザ名は隠した。

 なんと、実行したCGIは書き込み可能だと言ってる。書き込み可能なファイルは実行させないようにしてるの?だからログに残してるの?

 と、ふと思えば確かに自分のディレクトリの方は、ファイルは777だし、ディレクトリも777にしてある。これを両方とも755に変更して、もう一度ブラウザからアクセスしてみる。

hello!

 やっとうまく行った。書き込み可能だと実行できないなんて、知らんかった。

FF11メンバーリストCGI更新作業

 実は自宅でサーバーを稼働させてたりする。OSはVine Linux。一応24時間稼働で、コンテンツ的にはFF11というオンラインゲームの仲間内向けコミニュケーション用サイト。Wikiを中心に掲示板やらLSメンバー管理CGI等を動作させている。

 LSメンバー管理CGIは「瀬野部屋.com」様で配布している物を使用している。が、気づかないうちにかなりバージョンを上げていたことを、先日知り合いからの指摘により初めて気がついた。

 いろいろと変わっているところや追加になっているところがあるので、これはうちのサーバーで稼働しているCGIもバージョンアップしなければと思い手を付けてみる。

 現行サーバーで動作しているLSメンバー管理システムのバージョンは「0.5c2」。瀬野部屋.comで現在配布中のバージョンは「0.53au」。

アップデート方法の確認

 CGIのアーカイブに付属しているテキストファイルに、アップデートのやり方が簡単に書いてあったりする。これを見ると、ポイントになるのは「setup.cgi」と「data.cgi」の二つのファイル。配布されているCGIのアーカイブを解凍してアップロードしたあとに、設定変更などで書き換わっているのは、この二つのファイルなんだそうだ。

 特に「data.cgi」は、メンバーの名前やジョブレベル、スキル値などが記録されており、このファイルがほとんど命と言って差し支えない。「setup.cgi」の方は、マスターモードから変更できる部分がほとんどなので、実のところ上書きしてしまって、あとからマスターモードで変更する手順でも問題無い。むしろ上書きしないと、ページトップのメニューに、機能追加されたミッション一覧のメニューが出現しない(メニュー一覧もsetup.cgiに記述されているため)。

 このほか、diaryディレクトリ配下には各メンバーの日記データが入ってるが、こちらは配布されているアーカイブを展開しても上書きされることは無い(配布データの中には入ってない)ので、そのまま残しておいて問題無い。

アップデート作業

 次の手順でアップデート作業を行なった。

     
  1. まず、稼働中のディレクトリをバックアップする。
      以下、説明上稼働中のディレクトリ名は「guildmember/」。  
  2. 入手済みの新バージョンCGIをPC上で展開しておき、FTPでアップロードしておく。
      以下、説明上アップロード先ディレクトリ名は「guildmember053au/」。  
  3. 稼働中のディレクトリを別ディレクトリにコピーする。  
    mkdir guildmember_new
    cp -r guildmember/* guildmember_new/
     
  4. コピーしたguildmember_newに対して、新バージョンのファイルを解凍したディレクトリを上書きコピーする。
     
    cp -rf guildmember053au/* guildmember_new/
     
  5. 「data.cgi」を再度稼働中ディレクトリから上書きコピーする。  
    cp -f guildmember/data.cgi guildmember_new/
     
  6. 新バージョンを上書きしたディレクトリ内のファイルに対して、パーミッションを設定する。設定するパーミッションは、配布CGIのアーカイブに含まれるテキストファイルに従う。また、このほかにdiaryディレクトリ配下のファイルに対して、666のパーミッションを与える。  
  7. この時点で、新ディレクトリに対してインターネット経由でアクセスして正常に動作することを確認する。  
  8. うまく動作することが確認できたら、現在稼働中のディレクトリ名を別名に変更し、新バージョンのディレクトリを現在稼働中のディレクトリ名に変更する。  
    mv guildmember/ guildmember_old/
    mv guildmember_new/ guildmember/

発生した問題

 あとで気づいたのだが、登録順番トップの人のanonモード(レベルデータの公表設定)が勝手にON(隠す)になっている模様。原因は不明。マスターモードから設定を変えて対処した。

 あと、LSメンバーから言われたのは、どうも日記等の更新が前より時間がかかるらしい。CGIでかくなってるのかなぁ。まぁ機能も増えてるし、いたしかたないと思う。

BCB StarTeam 2005 Edition

 先日、めちゃめちゃ久々に秋葉原なんぞ行ってみた。ブラブラと店の中を歩いてると、こんなものが目に入った。

Borland C++Builder 6 Professional StarTeam 2005 Edition

 え。何これ。

 とうとうBCBの新しいのが出た?

 にしては、バージョン番号は6のまんまだし・・・・。

 で、家に帰ってからボーランドジャパンのサイトを探してみたけど、そんな製品出た話なんて見つからないし・・・。

 とりあえずアマゾンのページには、「「C++Builder 6」と「Borland StarTeam 2005 Standard」のバンドル製品」なんて書いてある。BCB自体は変わってないのね。

 チーム開発なんてやんないし、StarTeamはいらんのかなぁ自分には。

 BCBの次のバージョンはいつ出るんかねぇ。

 作ってた人MSに行っちゃったらしいし(人の噂でそう聞いた)、もうボーランドはC言語製品は作らんのかな。っていうか作れないんかな。

【BCB】HTTPダウンロード

 HTTPはHTTPクライアントがHTTPサーバーと会話するためのプロトコルだと言うことは知ってるつもり。だけど、具体的にどういう会話をしてるのかとかはほとんど知らない。

 そんな自分だが、とある事情でどうしてもHTTPダウンロードしなくてはならなくなった。しかも自動化することが前提なので、プログラムから固定のurlの物をユーザにメッセージを出すことなく取ってくるということをことをしなければいけない。

 で、いつもの事ながら調べた結果をブログに残しておく。ネット関係の用語が一部間違ってるかも。

概要

 BCB6 ProのIDEのコンポーネントパレットには「Indy Clients」というパレットがある。他のバージョン等でどうなっているかは知らない。

 このパレットの中に「IdHTTP」というコンポーネントがある。これがHTTPクライアントコンポーネントである。こいつを使えば簡単に実現できるらしい。

コーディング

 実際に次のソースで、うまくダウンロードできた。例えば画像をダウンロードしてくると、ちゃんと画像ファイルとして保存できた。ちょっとした感動。

 ただHTMLファイルなんかでは文字化けしていた。というか、例えばサーバー上でEUCコードで保存されているHTML文書は、EUCコードの文書としてクライアントに送られてくるんだろうね、たぶん。

 まぁとにかく、ソース自体はできあがってみると予想以上に簡単である。

  TIdHTTP *httpc = new TIdHTTP(NULL);
  TMemoryStream* stream = new TMemoryStream();
  AnsiString filename = "d:\\work\sample.txt";
  AnsiString url = "http://www.google.co.jp/";
  __try{
    httpc->Get(url,stream);
    stream->Seek(0,soFromBeginning);
    stream->SaveToFile(filename);
  }
  __finally{
    delete httpc; delete stream;
  }

ポイント1:IdHTTPオブジェクトの生成

 コンストラクタの引数は自コンポーネントのオーナー。ただ、もともと非ビジュアルコンポーネントなので、NULLにしておいて構わないらしい。

ポイント2:TMemoryStreamオブジェクトの生成

 HTTPサーバーから取り出したデータを格納するのに、TMemoryStream型を使う。なので、あらかじめnewでオブジェクトを生成しておく。

ポイント3:Get()メソッドでデータをゲット

 IdHTTPのGet()メソッドの引数に、取得したいurlとTMemoryStreamオブジェクトのアドレスを指定すると、ダウンロードしてきたデータが勝手に入る仕組みになっている。

 もちろん、ダウンロードしてきたデータはファイルに保存しなければならないので、TMemoryStreamのSaveToFile()メソッドで保存する。

ポイント4:エラー処理

 どこかの記事を見ると、処理中に何らかの問題が起きた時は例外で処理されるらしい。なので、__try〜__finallyで網をかけておいて、例外が発生した時でも必ずオブジェクトを破棄するようにしておく。

proxyサーバーを通す場合

 会社の環境が、社外ネットにアクセスするときにproxyサーバーを通らなければならず、そこに認証が必要となっている。これは、IdHTTPコンポーネントのプロパティに「Request」プロパティ(IdHeaderInfoクラス)というのがあり、そのプロパティの中にproxy関連のものもあるので、それを利用することでproxyも利用可能となる。

httpc->Request->ProxyUsername = "proxy認証ユーザ名";
httpc->Request->ProxyPassword = "proxy認証パスワード";
httpc->Request->ProxyPort = proxyポート番号(int型);
httpc->Request->Server = "proxyサーバーのurl";

ヴァナ・ディール時間(クラス作成編)

 今回は前回の続き。ヴァナ・ディール時間を取り扱うためのクラスを作ってみる。使用するコンパイラはBCB6。クラスライブラリはVCLで。

 ※2008.4.6修正:
  最近Visual C# 2008 Express Editionを入手したので、C#ベースで記事を書き直しする。

調査方法

 FF11のツールとして有名な「ばなな」を参考に調査する。

 ちなみに、「ばなな」の配布サイトはこちら

月齢の計算方法

 前回の記事で月齢の計算方法の調査が不足しているので、追加する。

 月齢は7日単位で変化して、12個の月齢で一回りする。なので単純に、

 vmoon = (vtime/7L)%12L;

 vmoonは計算結果となる月齢値、vtimeは天晶歴元年元日からの通算日。

 整数の横のLはlong型のリテラル値と言う意味。VC#では、longは符号付き64ビット整数である。ちなみにLを付けないとint型のリテラル値という意味で、intは符号付き32ビット整数となる。

 この式で天晶歴元年元日のvmoon値は0となる。0="新月"として表示させて、ばななの計算結果と比較してみた。

とりあえずの(間違った)計算式による月齢
天晶歴1044年11月16日〜11月23日 下弦の月
天晶歴1044年11月23日〜11月30日 二十日余月
天晶歴1044年11月30日〜12月06日 二十六夜月
ばななによる正しい月齢
天晶歴1044年11月18日〜11月25日 二十六夜
天晶歴1044年11月25日〜12月02日 新月
天晶歴1044年12月02日〜12月09日 三日月

 ・・・微妙にずれているorz

 まずは、11/18で月齢が変わるように計算式を微調整する。現在の計算式では11/18から月齢が変わっている物を11/18から変化するようにしなければならない。通算日から2を引くとぴったり来るようだ。

 vmoon = ((vtime-2L)/7L)%12L; 

 この計算式で、11/18〜11/25までが下弦の月と出る。実際の二十六夜月は下弦の月の2つ先の月齢なので、12の余りを求める前に2を足しておく。

 vmoon = (int)((((vtime-2L)/7L)+2L)%12L); 

 これで大丈夫だと思う。

クラス作成

 欲しい機能としてはこんな感じか。

  • 現在のシステム時間を元に、地球時間、ヴァナ・ディール時間の年月日時分秒ミリ秒を計算する。
  • 任意の地球時間を元に、ヴァナ・ディール時間を計算する。
  • 任意のヴァナ・ディール時間を元に、地球時間を計算する。
  • 上記の他、曜日や月齢(ヴァナ・ディール時間のみ)の計算もする。
  • 時刻文字列も生成する。

処理の仕方

 VC#で時間を扱う際は、DateTime構造体を使う。

 ちなみにこのDateTime、構造体とカテゴライズされながらメソッドを持っている。メソッドを持ってるならクラスじゃないのかと文句の一つも言いたくなったが、C#的には構造体もメソッドを持つらしい。クラスを軽量化したのが構造体の位置付けのようだ。

 話を戻すと、DateTime構造体はTicksというプロパティを持っている。このTicksプロパティ、「西暦元年1月1日0時0分0秒からの、100ナノ秒単位の経過時間」なんだそうだ。VCLのTDateTimeよりも高精度だったのに少し驚いた。

 このTicksプロパティをベースにして処理を行なうことにした。Ticksプロパティはlong型。上記の式でリテラル値にLを付けていたのは、これが原因である。

過去に発生していた問題

 以前この記事をBCBベースで書いていたときに、ヴァナ時間の1秒単位の計算をさせようとするとどこかで丸められてしまったかのように無視された計算結果を出されてしまう現象が出ていた。

 少なくとも今回VC#2008を使う限り、その辺の誤差は全く出ていないようである。めでたしめでたし。

別なずれ方?

 例えば現在時間を各プロパティにセットするときの処理で、作成当初は単純に

  1. DateTime構造体でTicks値を取得
  2. DateTime構造体を使って地球時間を算出
  3. Ticks値を使ってヴァナ時間を算出

 と、していた。ところがこの方法だと、現在時間をセットした後に地球時間->ヴァナ時間の変換をすると、ヴァナ時間のミリ秒のところが異なる結果を出してしまった。

 で、いろいろ試行錯誤してたどり着いた原因は、「地球時間の1ミリ秒は、ヴァナ時間では25ミリ秒に相当するため。」

 どういうことかというと、最初現在時間をセットするときは100ナノ秒単位のTicks値を使ってヴァナ時間を計算するのだが、地球時間->ヴァナ時間の変換をするときはミリ秒単位に丸められた地球時間を使ってヴァナ時間を計算するため、ここでズレが発生するわけである。

 そんなわけで、一度Ticks値から(ミリ秒単位に丸められた)地球時間を算出したら、その地球時間を元にTicks値を算出し直す処理を各所に埋め込んだ。これでズレはなくなるはずだ。

TVanaTimeクラス完成

 ダウンロードはこちらから。

 アクセスできるプロパティは、

int EYear;      //地球時間の年
int EMonth;     //地球時間の月
int EDay;       //地球時間の日
int EHour;      //地球時間の時
int EMin;       //地球時間の分
int ESec;       //地球時間の秒
int EMSec;      //地球時間のミリ秒
int EWDay;      //地球時間の曜日(0-6:日~土)
string EWDayString;     // EWDayの文字列バージョン(リードオンリー)
int VYear;      //ヴァナ時間の年
int VMonth;     //ヴァナ時間の月
int VDay;       //ヴァナ時間の日
int VHour;      //ヴァナ時間の時
int VMin;       //ヴァナ時間の分
int VSec;       //ヴァナ時間の秒
int VMSec;      //ヴァナ時間のミリ秒
int VWDay;      //ヴァナ時間の曜日(0-7:火~闇)
string VWDayString;     //VWDayの文字列バージョン(リードオンリー)
int VMoon;      //ヴァナ時間の月齢値
string VMoonString;     //VMoonの文字列バージョン(リードオンリー)

 利用可能なメソッドは、

void SetNow()          //現在時刻をE/V時間各プロパティ、btimeプロパティにセットする
void SetFromETime()    //E時間を元にBTimeプロパティとV時間をセットする
void SetFromVTime()    //V時間を元にBTimeプロパティとE時間をセットする
void SetFromBTime()    //BTimeプロパティを元にE/V時間をセットする
string VDateString()   //ヴァナ時間の日付文字列
string VTimeString()   //ヴァナ時間の時間文字列
string EDateString()   //地球時間の日付文字列
string ETimeString()   //地球時間の時間文字列
void AddVDay(int i)    //現在セットされているBTime値にヴァナ時間のi日分を足す
void AddEDay(int i)    //現在セットされているBTime値に地球時間のi日分を足す

 使い方は、

  • まずVTimeクラスのオブジェクトを作成   
  • 現在時間をプロパティに反映させたいときはGetNow()メソッドを実行する。   
  • 地球時→ヴァナ時変換をするときは、地球時間プロパティの年月日時分秒ミリ秒に値をセットしてSetFromETime()メソッドを実行する。   
  • ヴァナ時→地球時変換をするときは、ヴァナ時間プロパティの年月日時分秒ミリ秒に値をセットしてSetFromETime()メソッドを実行する。   
  • 曜日や月齢は、時間変換を実行したときに自動的に計算される。   
  • 日付時刻文字列化のメソッドは、プロパティに設定されている値を元に日付時刻文字列を生成する。

おまけ

 検証用にヴァナ時地球時変換プログラムを作った。せっかくなので遊んでみる。

FF11本サービス開始日

 確か2002年5月16日だったと記憶している。計算してみるとこの日の0時0分0秒は、ヴァナ時間だと天晶歴895年5月16日0時0分0秒だったりする。たまたまなのか、月と日が一致している。

 βの開始日っていつだったんだろう・・・。FF11用語辞典様を参照させていただいたところ、2001年12月17日~2002年4月26日だったそうである。こちらは特段変わった日付ではないようだった。

水晶大戦

 天晶歴862年の出来事である(参考:アルタナの神兵のプロモーションムービー)。西暦では2001年1月20日。FFXの発売(2001年7月19日)より前である(笑)。

 FFXの特典DVDの中にFFXIオンラインのβテスト募集とかのムービーが入っていた気がするから、α版でも開始した日なんだろうか。知る由も無し。

ミレニアム

 今日現在で天晶歴979年である。天晶歴1000年1月1日は、西暦2006年6月30日(金)14時24分と出た。来年の6月なら、まだサービス続いてそうだな。っつーか平日昼間だけど。

 ちなみに天晶歴1100年は西暦だと2010年になる。ここはムリだろうさすがに。当初この記事を書いたとき、2010年まで続くのかと思ったものだが、2007年末にアルタナの神兵が発売されており、今日現在(2008.4.6)で2010年まで続くんじゃないかという気になっている(あくまでも個人的に)。

ヴァナ・ディール時間(下調べ編)

 最近はご無沙汰しているが、以前FINAL FANTASY XIというゲームをしていた。このゲーム、シリーズ初のオンラインゲームで、規模も国内では結構大きい方らしい。

 このゲームは、ヴァナ・ディールという仮想世界を生きる冒険者の物語という設定で話が進む。ヴァナ・ディール内では、地球の時間とは違う独特の時間が流れている。ヴァナ・ディール時間とでも言うべきか。ゲーム内ではこの時間が重要なところがあり、出現するモンスターが変化したり、アイテム合成の成功率が変わったり、この時間に沿って定期運行している乗り物があったりと、プレイ中にかなり意識することになる。

 ユーザー発のサポートツールで地球時間を元にヴァナ・ディール時間を表示するアプリなどもあったりする。

 以前、知り合いと話している中で、ヴァナ・ディール時間の時計アプリを自作できないかという話になったことがあり、いろいろと調べたことがある。

 やはりと言うか、ネット上を検索してもヴァナ・ディール時間に関する情報が少なく(アプリのダウンロードはたくさん検索にかかるが)、オフィシャルにもそのあたりは語られていなかったので、一応記録を残す意味で調べた結果をブログとして書いておこうと思う。

参考にしたサイト

 結局見つかったのは次の二つ。サイト様の了承は取ってないがリンクしておく。

     
  • WATANABE Tetsuya HOME Page このページの「ソースコードのversion情報」→「ヴァナディールの時間は?」で、ヴァナ・ディール時間を表示するRubyスクリプトのページに行ける。  
  • Vana'diel Window Plus このページは上の「WATANABE Tetsuya HOME Page」でも参考にしている模様。リンクが張ってあった。

ヴァナ・ディール時間

 ヴァナ・ディール時間は、次のようになっている。

     
  • 1時間は60分  
  • 1日は24時間  
  • 1ヶ月は30日  
  • 1年は12ヶ月  
  • 1週間は8曜日(火、土、水、風、氷、雷、光、闇)

 地球時間(いわゆるグレゴリオ暦?)と違い、1ヶ月が30日だったり31日だったりすることが無く、閏年のように計算が複雑なことも無い。

ヴァナ・ディール時間と地球時間の関係

 実際、ゲームの画面に表示されるヴァナ・ディール時間は、かなりの早さで進んでいるのが見える。ほとんど地球時間の2秒前後でヴァナ・ディール時間の1分が経過する。

 ゲーム内の時計を見ていると、地球時間の毎時丁度(つまり1時00分とか、2時00分とか)は、ヴァナ・ディール時間でも同じ時刻である事がわかる。  ただし、注目しなければいけないのは日付の方で、例えば地球時間で1時の時にヴァナ・ディール時間も1時となった後、地球時間の2時の時は、ヴァナ・ディール時間では'''翌日の2時'''となる。

 言い換えると、地球時間で1時間経過すると、ヴァナ・ディール時間では25時間経過している。

     
  • 地球時間の1時間はヴァナ  
  • ディール時間の25時間  
  • 地球時間の1分はヴァナ・ディール時間の25分  
  • 地球時間の1秒はヴァナ・ディール時間の0.41666・・・秒

 逆にヴァナ・ディール時間を基準に考えると、

     
  • ヴァナ・ディール時間の1時間は地球時間の2.4分  
  • ヴァナ・ディール時間の1分は地球時間の2.4秒

 となる。

天晶歴

 ゲーム内で「/clock」コマンドを利用すると、メッセージウィンドウにヴァナ・ディール時間と地球時間が一緒に表示される。例えば、

 ヴァナ・ディール時間:天晶979年4月17日 1時00分
 地球時間:2005年9月5日10時36分00秒

 ヴァナ・ディールでは天晶という年号を使っている。今日現在で天晶979年である。

 天晶元年は、地球時間で言うといつなのか。プログラム上ベース時間にも使えそうなので、計算してみることにする。

 方法としては、現在のヴァナ・ディール時間が天晶元年1月1日0時0分から何分経過しているかを計算し、その経過時間を地球時間に換算して求める。

     
  • 天晶979年ということは、丸978年は既に経過している。978年=506,995,200分(978*12(ヶ月)*30(日)*24(時間)*60(分)=506,995,200分)  
  • 4月ということは丸3ヶ月は既に経過している。3ヶ月=129,600分(3(ヶ月)*30(日)*24(時間)*60(分)=129,600分)  
  • 17日ということは、丸16日は既に経過している。16日=23,040分(16(日)*24(時間)*60(分)=23,040分)  
  • 1時ということは、丸1時間は既に経過している。1時=60分  
  • 00分ということは、0分経過。

 合計すると、507,147,900分となる。これが天晶歴元年1月1日0時ちょうどから現在までの経過分数ということになる。  これは地球時間で言うと507147900/25=20,285,916分である。今度はこの分数を地球の日付に変換する。

     
  • 20,285,916分を60(分)で割ると、商が338,098、余りが36である。  
  • 338,098(時間)を24(時間)で割ると商が14,087、余りが10である。

 つまり、天晶歴元年1月1日0時ちょうどから現在まで、地球時間で14087日と10時間36分経過しているという事である。現在時間は10時36分として計算しているので、日付だけ考えれば良さそうである。

 ここからは日付の引き算になる。机上の計算ではめんどくさそうなので、プログラムにやらせることにする。以下、BCB6でのソース。

    TDateTime now;
    TDateTime past;
    unsigned short yy;
    unsigned short mm;
    unsigned short dd;

    now = TDateTime(2005,9,5); // 2005年9月5日で初期化
    past = TDateTime(14087.00); // 14087日0時間0分で初期化

    // 差し引いた結果をデコード
    now -= past; now.DecodeDate(&yy,&mm,&dd);

    ShowMessage(
      IntToStr(yy)+"年"+
      IntToStr(mm)+"月"+
      IntToStr(dd)+"日");

 結果は「1967年2月10日」と出た。これが天晶歴元年1月1日の時の地球時間である。

月齢について

 ヴァナ・ディールでは月齢も大きく影響する部分があるので、こちらも下調べしておく。

地球での月の話

 新月(真っ暗な月)からの経過時間を日数で表した数字のことを月齢と言う。また、月齢とは別に月相という言葉があり、こちらは月の明るい部分が満ち欠けで変化する様子を数字に表したもので、厳密には月齢と意味が異なる。

 そして、月齢と月相は必ずしも一致しない。例えば月相での14(満月)は、月齢で言うと13.8〜15.8の間で変動する。これは月の満ち欠けの速度が一定でないために起こる現象である。

 ちなみに、満月とか三日月とかは、月相に付けられた別名である。

ヴァナ・ディールの月の話

 地球の月とは異なり、月の満ち欠けの速度は一定で、かつ月齢とも完全に一致する。計算上は月齢=月相と考えて構わない模様。

 また、月の満ち欠けの仕方は地球の月のように徐々に変化するのではなく、ある時突然変わる。例えば満月だった月がある日の0時を越えた時点で突然十六夜月(ヴァナ・ディールでの満月の次の月齢)に変わる。

 ヴァナ・ディールにおいては、月相が次の12種類に限られる。また月相は7日周期で変化する。つまり、新月から次の新月までは、7×12=84日かかるということになる。月相が切り替わるのは、ヴァナ・ディール時間の0時である。

     
  • 新月  
  • 三日月  
  • 七日月  
  • 上弦の月  
  • 十日夜月  
  • 十三夜月  
  • 満月  
  • 十六夜月  
  • 居待月  
  • 下弦の月  
  • 二十日余月  
  • 二十六夜月

下調べ完了

 この辺でだいたい下調べはおしまい。次回は、この規則をベースにヴァナ・ディール時間計算クラスのコーディングをする。

【Forth】Forthのインタプリタを作るにあたっての下調べ

 最近、知人に「Forthのインタプリタ作ってみない?」と誘われた。目的は無く遊びで。まぁ趣味なので、それもアリということで。

 その時に軽くForthについての説明を受けたが、当然全部は頭に入るはずもない。「日本語の文章を書くと、そのままプログラムになるんだよ。」とか、「逆ポーランド記法っておもしろいよ。」とか、「中の動作はスタックそのものだよ。」とか、その辺の言葉はかろうじて私の頭に残った。

 で、その後にネットでその辺のキーワードを調べてみたけど、まぁとにかくForthに関する情報が少ないこと。Forthをベースにした派生言語の話はゾロゾロ出てきたけど。

逆ポーランド記法って何

 Forthでのソースの記述は、逆ポーランド記法で書くんだそうだ。じゃぁ逆ポーランド記法って何だろうってことで調べた。

 例えば、

 1 + 2 

 を、逆ポーランド記法で書くと、

 1 2 +

 と、書くんだそうで。「1と2を足す」みたいな。表現方法が日本語の文法に近いとかなんとか。いわゆる日本語プログラミング(プログラムのソースが日本語の文章みたいなやつ。)を語る上で、この逆ポーランド記法というのは重要なポイントらしい。

 もう少し複雑な式だと、例えば

 ( 1 + 2 ) * 5 

 なんていうのは、逆ポーランド記法で書くと、

 1 2 + 5 * 

 と書くらしい。「1と2を足して、その結果に5をかける」って感じ。

 括弧を使わない記述方法というのも利点の一つ。構文解析が不要で、頭から順に処理すれば事が済むのがいいそうだ。「括弧から先に計算しなきゃ」みたいなことはしなくてもいいらしい。

 もう一歩前へ。

 (1 + 2) * (3 + 4) 

 これを逆ポーランド記法で書くと、

 1 2 + 3 4 + * 

 あってんのかな・・・「1と2を足して、3と4を足して、かける」。意味は通じる?

スタック

 コンピュータの話でスタックと言うと、データ構造の一つであるスタックの事を言うのが普通。データを格納する領域なんだけど、格納の順番、取り出す順番に特徴がある。

 机の上に本を積み上げるような構造になっており、データを入れるときは一番上に追加され、データを出すときは一番上にあるデータを優先して出す。いわゆる「FILO」(ファーストイン・ラストアウト)方式のデータ構造。

 あれ、LIFO(ラストイン・ファーストアウト)だっけ?意味は一緒なはず。

 ちなみに、最初に格納されたデータが最初に取り出されるFIFO(ファーストイン・ファーストアウト)方式のデータ構造は「キュー」と呼ぶ。

スタックを利用した逆ポーランド記法のアルゴリズム表現

 逆ポーランド記法をプログラム化するときは、このスタックの仕組みを利用するのが一般的らしい。

 逆ポーランド記法で記述された数式を先頭から順に評価していくことにする。この時、次のルールに従うことにする。

     
  • 数字を一つ入力されたら、それをスタックに積む。  
  • 「+」や「*」といった演算子(C言語風に言うと二項演算子)が入力されたら、スタック上に積まれているデータを二つ取り出して計算して、計算結果をスタックに積む。

 以上のルールに従って、

 1 2 + 

 という計算をどのように行なうのか考えてみる。

 まず最初に数値「1」をスタックに積む。そして次に数値「2」をスタックに積む。最後に「+」が入力されたら、スタックに積まれている二つの値を取り出して足し算するので、「2」と「1」を取り出して足し算する。当然結果は「3」になる。その結果をスタックに積む。

 最終的にスタックの一番上には、1と2の和である「3」という数値が積まれている。これで一応計算ができた。

 では次の例。

 1 2 + 5 * 

 まず「1」を積む。次に「2」を積む。「+」が入力されたら「2」と「1」を取り出して、足した結果の「3」を積む。次に「5」を積む。「*」が入力されたら「5」と「3」を取り出して、かけた結果の「15」を積む。

 最終的にスタックの一番上には、計算結果である「15」が積まれている。

 このようにスタックの仕組みを利用すると、逆ポーランド記法をプログラムとして実装できるらしい。これから徐々にコーディングする予定。

【BCB】関数ポインタとメソッドポインタ

 関数のポインタを使うことができれば、動的に呼び出したい関数を切り替える事ができる。やり方がよくわからなかったので、ちょっと調べてみた。前提はBCB6。

関数ポインタの利用

 例えばint型の引数を一つ取って、戻り値の無い関数を用意したとする。プロトタイプ宣言は次の通り。

void MyFunc(int); 

 これを普通に呼び出すのなら、ソース上で、

MyFunc(5); 

 とすればいい。

 この関数をポインタ経由で呼び出す場合どうするかというと、

     
  1. まず関数のアドレスを格納するためのポインタ変数を一つ用意する。  
  2. 用意したポインタ変数に関数のアドレスを格納する。  
  3. 関数のポインタ変数を利用して関数を呼び出す。

 関数ポインタの宣言例は次の通り。

void (*ptrFunc)(int); 

 用意した変数に関数のアドレスを格納してみる。

ptrFunc = &MyFunc; 

 関数ポインタ経由で関数をコールする。

ptrFunc(5); 

 これで、MyFunc(5)を呼び出したのと同じ事になる。

クラスメソッドのポインタ

 クラスのメソッド(メンバ関数)を上記と同様のやり方でやると、エラーが出てしまう。

 例えば次のようなソースをコンパイルしてみる。


void TForm1::MyFunc(int a){
    ShowMessage("Hello.");
}
void TForm1::Button1Click(TObject *Sender) {
    void (*ptrFunc)(int);
    ptrFunc = &MyFunc;
    ptrFunc(5);
}

 そうすると、関数のアドレスをポインタに代入しようとするところで、

コンパイル
[C++ エラー] Unit1.cpp(xx):
E2034 'void (* (_closure )())()'
型は 'void(*)()' 型に変換できない

 というエラーになってしまう。クラスのメソッドは、型として「__closure」がデフォルトで付いてるらしい。ってことは、たぶんポインタの方にも__closureを付けてあげれば代入がうまくいくんじゃなかろうかと推測。

拡張キーワード「__closure」

 ヘルプで「クロージャ」を見てみた。どういった効能があるのか説明が難しすぎて正直なにがなにやら。とりあえずBCBのオリジナル拡張文法の部分らしい。

 とりあえずポインタに__closureを付けてコンパイルしてみる。


void TForm1::MyFunc(int a)
{
    ShowMessage("Hello.");
}
void TForm1::Button1Click(TObject *Sender)
{
    void (__closure * ptrFunc)(int);
    ptrFunc = &MyFunc;
    ptrFunc(5);
}

 さらに、関数のポインタを配列にしてみる。


void TForm1::MyFunc1(void)
{
    ShowMessage("Good morning.");
}
void TForm1::MyFunc2(void)
{
    ShowMessage("Good evening.");
}
void TForm1::MyFunc3(void)
{
    ShowMessage("Good night.");
}
void TForm1::Button1Click(TObject *Sender)
{
    void (__closure * ptrFunc[3])(int);

    ptrFunc[0] = &MyFunc1;
    ptrFunc[1] = &MyFunc2;
    ptrFunc[2] = &MyFunc3;

    (ptrFunc[StrToInt(Edit1->Text)])();
}

 TEditコンポーネントを一つ用意して、そこに入力された0〜2の値に対応した関数の呼び出しを行なう。3とか入力したら知らんけど。