【mercurial】ファイルごとの変更履歴を閲覧する

バージョン管理システムツールの mercurial

hg log 

を実行すると

changeset:   11:XXXXXXXX
branch:      tXXX
tag:         tip
user:        hydden
date:        Fri Apr 27 21:04:19 2012 +0900
summary:    XXフィールドを追加。

changeset:   10:XXXXXXXX
branch:      tXXX
user:        hydden
date:        Fri Apr 27 19:28:36 2012 +0900
summary:     XXで<br>タグが効くようにする

.
.
.

管理下のディレクトリ全体の変更履歴を閲覧できるが、

コマンドの後ろにファイルを指定すれば、

hg log hoge.txt

changeset:   11:XXXXXXXX
branch:      tXXX
tag:         tip
user:        hydden
date:        Fri Apr 27 21:04:19 2012 +0900
summary:     XXフィールドを追加。

changeset:   9:XXXXXXXX
branch:      tXXX
user:        hydden
date:        Thu Apr 26 09:53:26 2012 +0900
summary:     XXXの部分を修正

指定したファイルが変更された時の履歴のみ表示できることを最近知った。

別サーバにあるTracからRedmineへデータを移行する手順

経緯

先日、開発用サーバに立ててあるBTS(バグ管理システム)ツール"Trac"のデータを
別のサーバに立ててある同様のBTSツール"Redmine"に移行する事になった。


Redmine公式の"他システムからの移行"ページによると、
tracからredmineへの移行を実行するスクリプトがあり、それを利用した移行手順が載っているが、
これは同一サーバ内にTracRedmineが同居していることを前提とした手順なので、
別サーバに立ててある場合についての手順は載ってなかった。


先輩に訊いてみたところ、バグチケットの移行の例はあったものの、
Wikiを含めたデータを丸ごと移行した前例はないとの事だったので
今回調査してここに移行手順を載せることにする。


この手順では、以下の前提に則って書いてあるのであしからず。


移行手順

1.tracがあるサーバにて

tracディレクトリを"tar.gz"で圧縮して、ユーザディレクトリに移動させる

  $ sudo su - #rootユーザに変更
  $ cd /var/
  $ ./stop_trac.sh #圧縮中に更新されないようにTracを止めておく
  $ tar -zcvf trac.tar.gz trac
  $ mv trac.tar.gz /home/[ユーザ]/

2.ローカルPCにて

tar.gzファイルをscpで持ってくる

  $ scp [ユーザ]@[tracがあるサーバ]:~/trac.tar.gz .

tar.gzファイルをredmineがあるサーバにscpで送る

  $ scp trac.tar.gz [ユーザ]@[redmineがあるサーバ]:

3.Redmineがあるサーバにて

Redmineがあるサーバでtar.gzファイルを展開する

  $ cd /home/[ユーザ]/
  $ tar -zxvf trac.tar.gz

もし失敗した時のためにRedmineのDBのバックアップをとっておく

  #DBがmysqlの時
  $ mysqldump -u[ユーザ名] redmine > redmine.dump2012XXXX

オプション設定。以下の条件が当てはまっていたら、それぞれに対応した※(後述)を実行する。

  • (Tracのバージョンが0.12の場合 ※1)
  • (TracのデータをSqlite3で格納していた場合 ※2)
  • (RedmineのサーバのRubyバージョンをRVMで切り替えている場合 ※3)

データ移行スクリプトを実行する

  $ cd /var/www/[redmineのパス]
  $ sudo su - www-data                      #www-dataユーザに変更
  $ mkdir files                                         #実行する時に必要なディレクトリを作成
  $ rake redmine:migrate_from_trac RAILS_ENV=“development”
     #実行後以下の問い合わせが来るのでそれぞれ入力する
     Trac directory []:  #Tracが置いてあるパスを入力
     Trac database adapter (sqlite, sqlite3, mysql, postgresql) [sqlite]: #Tracで使用したDBの種類を入力
     Database encoding [UTF-8]: #DBの文字コードを入力(何も入力せずEnter押すだけでもOK)
     Target project identifier []: #Redmine上で使うプロジェクトの名称を入力
     ↓
     Trac directory []: /trac/home/[ユーザ]/trac/[プロジェクトのディレクトリ]
     Trac database adapter (sqlite, sqlite3, mysql, postgresql) [sqlite]:sqlite3
     Database encoding [UTF-8]:
     Target project identifier []: [プロジェクト名] 

入力すると処理が走って、数分経てば完了する。

                                                  • -

※1 Tracのバージョンが0.12の場合:
移行スクリプトは0.12をサポートしていない。
[参考URL: http://www.redmine.org/issues/6868 http://www.redmine.org/issues/5764 ]
なので、スクリプトファイルを一部修正しなければいけない。

  1). スクリプトファイルを修正:
  $ sudo su -  #rootユーザに変更
  $ vi /var/www/[Redmineのパス]/lib/tasks/migrate_from_trac.rake
     78           def fake(time)
     79             @fake_diff = real_now - time

     ↓ time = 0 if time.nil? を追加

     78           def fake(time)
     79             time = 0 if time.nil?
     80             @fake_diff = real_now - time

  2). パッチファイルを当てる
  $ wget http://www.redmine.org/attachments/download/4049/migrate_from_trac.diff
  $ patch -p0 < migrate_from_trac.diff
    パッチを当てるファイル名の問い合わせに migrate_from_trac.rake を指定
                                                        • -

※2 TracのデータをSqlite3で格納していた場合:
Ruby用のSqlite3をインストールする

  $ sudo gem install sqlite3-ruby
                                                        • -

※3 RedmineのサーバのRubyバージョンをRVMで切り替えている場合:
RVMでバージョンを指定しないと、スクリプトを認識してくれないので指定する。

  $ cd /var/www/[redmineのパス]
  $ sudo su - www-data
  $ rvm use 1.8.7
                                                      • -

FAQ 移行に関して出てくるありがちな疑問

Q1.TracのユーザがRedmineに登録されなかった。どうして?
 A.Trac上でユーザが作成されていても、チケットの担当が割り振られていないとRedmine上にユーザは作成されない。
  ユーザを移したい時はどこかしらのチケットに割り振るように。


Q2.TracRedmineでユーザIDが重複したら、ユーザ情報(ログインパスワードなど)は上書きされちゃうの?
 A.Redmine側のユーザ情報は保持される。Tracで受け持ってるチケットを引き継いでくれるので意外と便利。

ディレクトリ下のファイルのowner:groupeを一括変更

帰宅途中での書き込み。


hogeディレクトリとそれ以下のファイルのowner:groupeを変更するときに、いちいち

$ chmod hyde:dev hoge
$ chmod hyde:dev hoge/*
$ chmod hyde:dev hoge/*/*
...

とやって面倒なことをしてたけど、
今更ではあるがコマンドオプションに -Rを入れれば一回実行するだけでhoge内全部に適用されるのを知った。

$ chmod -R hyde:dev hoge

先週python温泉行ってきました。

先週の金曜日から3日間(2011-8-05〜07)、
来宮(熱海)にて@voluntasさん主催ののpython温泉(通称pyspa)という勉強会に会社の皆と参加しました。
場所は芳泉閣というひなびた所でしたが、偶然にも昔親父が国鉄時代に
仕事を持ち込んだことがある旅館でした。


自分でやりたいタスクを持ち込むということで、
Pygameの参考書とMacbookAirと楽曲制作用のWindowsVistaノートを持ってゲームを一つ作ろうと思ったんですが、
意外と時間が経つのが早く、そして同じ参加者の方が持ってきたアナログゲームにハマってしまい、
結局は参考書のサンプルコードの写経しかできませんでした。。


まあ、雰囲気は修学旅行のそれに近くとても面白かったので、参加してよかったです。
次回も参加したいのですが、どうやら最終回らしいので参加できるか不安ではあります。

pygameで音を鳴らす

来週社内勉強会で発表する機会を先日頂いたので、
Pygameで特に私が興味を持っている音声制御機能pygame.mixerモジュールについて
調べてみて、こういうアプリに使えるんじゃね?という事を、
発表のカンペも兼ねて書いていきます。
なお、作成には
Will McGugan著 杉田臣輔・郷古泰昭 訳「Pythonゲームプログラミング入門」(2011)アスキーメディアワークス
を参考にしてます。


そしてこの記事を、共同で仕事をする約束したが、果たすことなく
先日急逝された高校時代の友人 @8841028 氏に捧げます。


1.Pygameとはなんぞや!?

Pygameの歴史

Pythonゲームプログラミング入門」p68より要約引用。

Pygameは、Simple DirectMedia Layer (SDL) というゲーム作成ライブラリによって構築されています。
SDLはプラットフォーム間でのゲーム移植のタスクを簡易化するために書かれました。
これは、グラフィックスや入力デバイスと複数のプラットフォーム上での表示と同様の処理を作成する一般的な方法を提供します。
1998年のリリース以来、簡単に動作するので非常にゲーム開発者に普及するようになり、趣味から商用のゲームまで多く使われてきました。
SDLC言語で書かれています。C言語は低レベルのハードウェアで動作する能力があるので、一般的にはゲームで使われていました。
しかし、CやC++で開発すると遅い上にエラーしがちです。現在は、他言語で利用出来るように関連付けられているため、
他言語でもSDLが利用出来るようになっています。Pygameに関連付けさせることで、pythonSDLライブラリを使用できます。

つまり、元々はC言語用に提供していたSDLライブラリだったが、
C、C++での開発では処理が遅くエラーが発生しやすいということなので、
提供する機能をそのままに、他の言語でも利用出来るよう関連付けをしたライブラリの一つが
Pygameということです。


2.pygame.mixerモジュール

そもそもミキサー(ミキシング)って?

複数の音源をもとに、ミキシング・コンソールという機材を用いて
音声トラックのバランス、音色などをつくりだす作業です。
そちらの方面はあんまり詳しくはないですが、
自分が作曲したことがある経験から言わせてもらうと、
ミックスした音声の設定をファイルに焼きつけてしまうので
動的に設定を変化させるのはむずかしいと思います。

その音声の設定を動的にプログラム内で行うのがpygame.mixerモジュールです。

pygame.mixerモジュールで使える関数

Channel            Channelオブジェクトを作成します。引数にチャンネル番号を取ります
fadeout            全チャンネルの音量を徐々に0まで減らします。フェード時間(ミリ秒単位)を引数に取ります
find_channel       現在未使用のチャンネルをさがし、そのチャンネル番号を返します
get_busy           任意のチャンネルで音声が再生されてる場合、Trueを返します
get_init           ミキサーが初期化されている場合、Trueを返します
get_num_channels   提供されているチャンネル数を取得します
init               ミキサーモジュールを初期化します。引数には、周波数(整数)、ビットレートサイズ(整数)、ステレオ(1or2)、バッファ(整数)
                   を取ります
pause              すべてのチャンネルの音声を一時停止します
pre_init           pygame.initへの呼び出しによって自動的に初期化する場合、ミキサーのパラメータを設定します
quit               ミキサーを終了します。これはPythonスクリプトの終了時に自動的に行われますが、他のパラメータを使用して
                   ミキサーを再び初期化する場合に呼び出す必要があります
set_num_channels   提供するチャンネルの数を設定します
Sound              Soundオブジェクトを作成します。ファイル名または音声データを含むPythonのファイルのオブジェクトを引数に取ります
stop               全チャンネルの音声の再生をすべて停止します
unpause            一時停止している音声を再開します

モジュールから生成されるオブジェクト

このpygame.mixerから生成されるオブジェクトは2つあります。

Sound
Channel

・Soundオブジェクト

オーディオデータの読み込みと再生のために使用されます。
音楽に例えると、楽譜を持った指揮者のような感じでしょうか。
読み込みに対応するファイル形式は

・WAV
・Ogg

の2つです。

hoge = pygame.mixer.Sound("hoge.WAV")
hoge.play()

と書けば、hogeにオーディオデータを格納したSoundオブジェクトが返され、playメソッドで再生されます。

・Channelオブジェクト

Pygame中で同時に再生できる音源のひとつです。
音楽に例えるとSoundオブジェクトが指揮者に対し、こちらは演奏者に当たると思います。
こちらは

channel = pygame.mixer.Channel(0) 

と呼び出せますし、
先の例に上げたhoge.play()は戻り値にChannelオブジェクトを返すので

channnel = hoge.play()

でも呼び出せます。

channel.set_volume(0.5)

と書けば、再生の仕方を細かく設定することができます。
特に細かく設定する必要がなければ、hoge.play()の戻り値を無視することも可能です。


また、デフォルトで同時発生できるチャンネル番号は0~7の計8つあり
どのチャンネルを鳴らすのかはpygame側に制御を任せるのですが、
特定のチャンネルだけに再生させたいSoundオブジェクトを優先して指定することが可能です。

pygame.mixer.set_reserved(1)
reserved_channel =  pygame.mixer.Channel(0)
reserved_channel.play(hoge)

0番目のチャンネルだけにhogeを鳴らすときはこう書きます。

各オブジェクトが利用出来るメソッド

Soundオブジェクト

fadeout            徐々にすべてのチャンネルのサウンドの音量を低減させます。引数にミリ秒単位の整数を受け取る
get_length         音の長さを秒単位で返します
get_num_channels   何回音声が再生されているかをカウント
get_volume         0.0~1.0の浮動小数点として音声の音量を返します
play               音声を再生します。引数にループ回数とミリ秒単位の最大再生時間を受け取ります
set_volume         再生される音声の音量を設定します。パラメータは0.0~1.0の浮動小数点です         
stop               再生中の音声を停止します

Channelオブジェクト

fadeout            ミリ単位秒で指定された時間で音声をフェードアウトします
get_busy           音声が現在のチャンネルで再生されている場合はTrueを返します
get_endevent       サウンドの再生が終了したときに送信されるイベント、または終了したイベントがない場合はNOEVENTを返します
get_queue          再生のためのキューに入っている任意のサウンド、またはキューに入られた音声がない場合はNoneを返します
get_volume         0.0~1.0の単一の値としてチャンネルの現在の音量を取得します(ステレオ設定時は取得できない)
pause              このチャンネル上の任意の音声の再生を一時停止します
play               特定のチャンネルを再生します。Soundオブジェクトと繰り返し回数と最大再生時間を引数に取ります
queue              現在の音声が終了したときに特定の与えられた音声を再生します。引数にキューに入れるためのSoundオブジェクトを取ります
set_endevent       現在の音声の再生が終了したときにイベントを要求します。送信のためのイベントのidを引数に取ります。
                   idは既存のイベントとの衝突を避けるため、USEREVENT(pygame.localsの定数)以上の値にしなければいけません。
                   引数を指定しない場合は終了イベントの送信は行いません
set_volume         このチャンネルの音量を設定します。1つの値が指定されてる場合は、両方のスピーカに使用されます。
                   2つの値が指定された場合は左右のスピーカの音量が独立して設定されます。どちらも引数に0.0~1.0の値を受け取ります
stop               即座にチャンネル上のあらゆる音声の再生を停止します
unpause            一時停止しているチャンネルの再生を再開します

ステレオ効果を付けた音声再生デモ

※「Pythonゲームプログラミング入門」のp263~266サンプルコードより音声制御部分を引用。

(略)

def stereo_pan(x_coord, screen_width):
    right_volume = float(x_coord) / screen_width #画面の幅に対する現在のx座標位置の割合を、右スピーカのボリュームとして取得
    left_volume = 1.0 - right_volume              #1から右スピーカのボリュームを引いて、左スピーカのボリュームとして取得
    return (left_volume, right_volume)

class Ball(object):
    def __init__(self, position, speed, image, bounce_sound):
        self.position = Vector2(position)
        self.speed = Vector2(speed)
        self.image = image
        self.bounce_sound = bounce_sound
        self.age = 0.0
(略)
    def play_bounce_sound(self):
        channel = self.bounce_sound.play()

        if channel is not None:
            # Get the left and right volumes
            left, right = stereo_pan(self.position.x, SCREEN_SIZE[0])
            channel.set_volume(left, right)        # チャンネルに取得したボリュームを代入
(略)

def run():

    # 44KHz、16ビットのステレオ音声で初期化
    pygame.mixer.pre_init(44100, 16, 2, 1024*4)
    pygame.init()
    pygame.mixer.set_num_channels(8)              # チャンネル数を8に設定

(略)
    # 音声ファイルのロード
    bounce_sound = pygame.mixer.Sound("bounce.wav") # 音声ファイルをロードする
(略)

3.pygame.mixer.musicモジュール

pygame.mixer.musicはオブジェクトを作らない!?

pygame.mixerモジュールでは音楽ファイルを鳴らすことも可能ですが、ファイルを一度全部読み込んでから再生するので、
短いジングル程度ならともかく、BGMとして使うにはコンピュータのリソースに負担をかけてしまう傾向があり、
通常は音楽の再生には向いていません。
BGMの再生にはファイルを少しずつ読み込んで再生(ストリーミング)するpygame.mixer.musicサブモジュールが適しています。
pygame.mixer.musicモジュールはMP3とOgg形式の音楽ファイルを再生できます。
MP3のサポートはプラットフォームに依存するので各プラットフォームで
十分にサポートされてるOggを使用するのがいいでしょう。


音楽ファイルを読み込み、再生するには

pygame.mixer.music.load("music.ogg")
pygame.mixer.music.play()

と書きます。
ここで、pygame.mixerモジュールと違うのは読み込み時にオブジェクトを返さないところです。
複数の音楽ファイルを同時にストリーミングすることができないからです。
ロードしたファイルの操作はpygame.mixer.musicモジュールで行います。

pygame.mixer.musicモジュールで利用出来るメソッド

get_busy           現在音楽が再生されている場合にTrueを返します
fadeout            一定時間音量を減らします。引数にミリ単位秒の整数を取ります
get_endevent       送信された終了イベントを返します。またはイベントがないときは0を返します
get_volume         音楽の音量を返します。
load               再生する音楽ファイルを指定します。引数にオーディオファイルを取ります
play               loadで指定された音楽ファイルの再生を開始します。引数にループ回数と再生開始位置(秒単位)を取ります。
                   ループ回数に-1を指定した場合stopを呼び出すまでループし続けます
rewind             引数で指定した開始位置(秒単位)から音楽ファイルを再生します
set_endevent       音楽の再生が終了したときに送信できるイベントを要求します。送信するイベントのidを引数に取ります。
                   既存のイベントとの競合を避けるため、USEREVENT(pygame.localsの定数)以上でなければいけません。
                   引数がない場合、終了イベントを送信しません
set_volume         音楽の音量を設定します。0.0~1.0の値を引数に取ります。新しい音楽が読み込まれると、1.0にリセットされます
stop               音楽の再生を停止します。
unpause            一時停止している音楽を再生します
get_pos            再生している音楽の再生時間をミリ単位秒で返します
pause              音楽の再生を一時停止します
queue              再生中の音楽が終了したときに再生される曲を指定します。引数に再生したい音楽ファイルを取ります

BGM再生デモサンプル

※「Pythonゲームプログラミング入門」のp269~273サンプルコードより音楽制御部分を引用。

(略)
def run():
    # 44KHz、16ビットのステレオ音声で初期化
    pygame.mixer.pre_init(44100, 16, 2, 1024*4)
    pygame.init()

(略)
    # 最初のトラックをロードする
    pygame.mixer.music.load(music_filenames[current_track])

    clock = pygame.time.Clock()
    
    playing = False
    paused = False

    # 音楽のトラックが終わったときにこのイベントが送信される
    TRACK_END = USEREVENT + 1
    pygame.mixer.music.set_endevent(TRACK_END)

    while True:
    
        button_pressed = None

        for event in pygame.event.get():
        
            if event.type == QUIT:
                return 

            if event.type == MOUSEBUTTONDOWN:
                
                # 押下されたボタンを探す
                for button_name, button in buttons.iteritems():
                    if button.is_over(event.pos):
                        print button_name, "pressed"
                        button_pressed = button_name
                        break

            
            if event.type == TRACK_END:
                # 曲が終了したとき、'次へ(next)'ボタンの押下をシミュレートする
                button_pressed = "next"

        if button_pressed is not None:
        
            # '次へ(next)'ボタンが押下されたとき、次の曲へ進む
            if button_pressed == "next":
                current_track = (current_track + 1) % max_tracks
                pygame.mixer.music.load(music_filenames[current_track])
                if playing:
                    pygame.mixer.music.play()

            # '前へ(prev)'ボタンが押下されたとき、前の曲に戻る
            elif button_pressed == "prev":

                # 曲が3秒より長く再生されたときは巻き戻し
                # そうでなければ、前の曲を選択する
                if pygame.mixer.music.get_pos() > 3000:
                    pygame.mixer.music.stop()
                    pygame.mixer.music.play()
                else:
                    current_track = (current_track - 1) % max_tracks
                    pygame.mixer.music.load(music_filenames[current_track])
                    if playing:
                        pygame.mixer.music.play()

            # '一時停止(pause)'ボタンが押下されたとき、一時停止。もう一度押下されたら、停止位置から再生
            elif button_pressed == "pause":
                if paused:
                    pygame.mixer.music.unpause()
                    paused = False
                else:
                    pygame.mixer.music.pause()
                    paused = True
            
            # '停止(stop)'ボタンが押下されたとき、曲の再生を止める
            elif button_pressed == "stop":
                pygame.mixer.music.stop()
                playing = False

            # '再生(play)'ボタンが押下されたとき、曲を再生する
            elif button_pressed == "play":
                if paused:
                    pygame.mixer.music.unpause()
                    paused = False

                else:
                    pygame.mixer.music.play()
                    playing = True

(略)

4.今後の展望

こんなアプリが作れるかも

音声を読み込んで再生させるだけなら、アドベンチャーゲーム等のは作れるでしょう。
しかし、ただ再生させるだけでなく、どう再生させるかで音声再生の用途の幅は増えていきます。
Pygameだからといって、ゲームにしか使えないというわけではなく、ゲーム以外でも活躍はできると思います。


pygame.mixerモジュールを使えば例えば、
使用者の向いている方向を計算し正しい経路のある方向から音が鳴るという、
視覚障害の方のための音声案内ナビゲーションアプリが作れるのではないでしょうか。


また、そのナビゲーションアプリをゲーム側にフィードバックすれば、
目標物を発生する音を頼りに探す、宝探しゲーム等も作れそうです。

MySQLレプリケーション設定の最後の所でハマった時の対処法

・使用環境:
Debian 2.6
MySQL 5.1


CASE:
とある案件でMySQLレプリケーションを作ることになった。


MySQL公式サイトの レプリケーションの設定 の手順通りに進めていったのだが、最後らへんの

#MySQL上のコマンド
mysql > show slave status;

を実行した時に、

#抜粋
Last_IO_Error  :  error connecting to master 'repl@[マスタDBが置いてあるIPアドレス]:3306' - retry-time: 60  retries: 86400

が表示されていて、接続エラーを起こしているという。


スレーブ側の設定が悪かったのかと思って、考えつく限りの方法を(最初から設定し直すなど)試したが何も変わらず。
職場の先輩からのアドバイスで、スレーブ側サーバのコマンドライン上で

$ telnet [マスタDBが置いてあるIPアドレス] 3306

を実行したら、Connection Refused となって接続を拒否られていることを確認した。
拒否られる理由として上がったのが、マスタ側のMySQLのポート3306が外部に公開されていないということ。


ということで、マスタ側サーバのコマンドライン上で

$ netstat -ln

を実行したら

Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp              0          0 127.0.0.1:3306      0.0.0.0:*                    LISTEN 

アドレスが127.0.0.1で表示されていた。ポート3306 は、外部に公開されていないというのが判明した。
ポートが外部に公開されていない原因は、MySQLの設定でbind-adressを変更していないからだった。



REMEDY:
マスタ側サーバのMySQL設定を変更する。

$ sudo vim /etc/mysql/my.cnf

#書き換える
bind-address           = 127.0.0.1
↓
bind-address            = 0.0.0.0

MySQLを再起動する。

$ sudo /etc/init.d/mysql restart

これで、

$ netstat -ln

を実行したら

Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp              0          0 0.0.0.0:3306          0.0.0.0:*                    LISTEN 

127.0.0.1が0.0.0.0となって外部に公開されるようになった。
同様にスレーブ側サーバのMySQL設定も変更したら、うまくレプリケーションするようになった。

Djangoで自作の404エラーページや500エラーページを使う

Djangoで自作の404エラーページや、500エラーページを使いたい時に何か面倒な
設定を書かなきゃ行けないのかなと思っていたが、
プロジェクトディレクトリ内settings.pyの中身の

DEBUG = True

DEBUG = False

に書き換えて、
templatesディレクトリ下に

404.html
500.html

を作成するだけで完成。

簡単でしたねっ!