タイムアウト

非常に時間のかかる処理をゴルーチンにするといつまでも終わらなくなる可能性がある。selecttime パッケージの After を組み合わせると、タイムアウトの処理ができる。
After は一定時間が経過したらデータを送信するチャネルを返す。なので、select と併用することでタイムアウトの処理ができるわけだ。

ここでは、時間のかかる処理(関数)として、フィボナッチ数を求める関数を用意した。これで5つのゴルーチンを起動しておき、その終了を待つと同時に time.After で500ミリ秒でタイムアウトするようにしている。つまり time.After の返り値であるチャネルからデータが送られてくる前に処理が終わったゴルーチンだけが結果を表示するわけだ。

^o^ > go run channel_after.go
9227465
24157817
63245986
Timeout

このとおり、500ミリ秒では3つしか終わらなかった。

Goで画像アップローダーを作ってみた

画像アップローダーっていうと、あれだ、ユーザーがアップロードした画像を保存しておいて掲示板かなんかから参照できるようになってるやつだ。あれ、なんでアップローダーっていうんだろうね。
ま、とにかく Go で WEB アプリケーションを書く練習に作ってみた。Go ってワンバイナリでサーバーが作れるからいいよね。
名前は Sulaimān (スライマーン)にした。

 cf. github.com/takatoh/sulaiman

シングルページアプリケーション

複雑なシステムではないので、html を読み込むのはルートにアクセスしたときだけで、あとはすべて ajax で非同期に更新するシングルページアプリケーションにした。
おかげでサーバーサイドの Go だけじゃなくて、クライアントサイドの JavaScript も結構書いた。

サーバーサイド

サーバーサイドの WEB フレームワークには Echo というのを使った。ググってみた範囲では、結構簡単そうだったし、速度も速いらしい。それからデータベースの OR マッパーには gorm というのを採用。Ruby や Python の OR マッパーとちょっと勝手が異なるけど、データベースといってもテーブルひとつの簡単なものだし、Go の中では割とメジャーなものみたい。

 cf. Echo
 cf. gorm

ほかにも JSON を扱うライブラリとか、画像のサムネイルを作るのにはこないだちょっと書いた resize を使った。
コードのすべては上でリンクした GitHub を見てもらいたいけど、メインのファイルだけ載せておこう。

設定ファイルやデータベースの読み込みをしたあと、Echo とハンドラーのインスタンスを作って、ルーティングの設定をしている。GET とか POST とかのメソッドでルーティングの設定をするのは Ruby の Sinatra なんかと雰囲気が似ている。

クライアントサイド

上に書いた通り、シングルページアプリケーションなので、いったんページを読み込んだ後はすべて ajax で非同期に処理をする。ページ遷移はない。
で、JavaScript のライブラリには定番の jQuery と、ページ描画のフレームワークには Vue.js というを使ってみた。jQuery は以前使ったことがあるけど Vue.js は初めてだ。

 cf. Vue.js

でも、Vue.js は学習コストが低いのも売りらしく、実際特に苦労することもなかった。もっとも大したことはしてないんだけども。

ちょっとひっかかった点

それでもうまくいかなかったところはあって、ひとつにはページのタイトルがあげられる。ページタイトルは設定ファイルから読み込んで表示するようにした。まあ、当たり前の発想だよね。で、どうやって表示しようかというときに、サーバー側でテンプレートを使うことを最初に考えた。Go には html/template というライブラリが標準であって、それを使おうとしたんだけど、テンプレートの構文が Vue.js のものと同じ(データを埋め込みたいところに {{ foo }} みたいに書く)で、共存ができなかった。仕方がないので、タイトルも ajax でとってきてクライアントサイドで更新するようにした。
もうひとつもクライアントサイド。次のページを読み込む Next リンクをクリックしたときの動作を、ajax で次のようにやろうとしたところ、うまくいかない。

これ、結構悩んでいろいろ調べて、結局こうなった。

なんでこうじゃなきゃ動かないのかわからない。あとで調べよう。
ともかくもこれで一通りはできた。

やり残したこと

たいていのアップローダーにはついている、ファイルの削除機能がない。一応、削除のためのキーをアップロードの際に入力するようにはしてあるので、今後実装するかもしれない。でもどんな UI にしようか悩んでいる。
それと、ページの下までスクロールしたら自動で次のページを読み込む、いわゆる無限スクロールも実装してみたい。
いつになるかわからないけど。

JPEG画像のリサイズ

Go で画像をリサイズしたいときには、github.com/nfnt/resize を使うといいらしい。

 cf. github.com/nfnt/resize

今回はこれを使って、JPEG 画像のサムネイルを作ってみた。サムネイルは 120×120 に納まり、アスペクト比を保つものとした。

少し引っかかった点が2つ。
1つは、rezise.Resize 関数のサイズを指定する第1、第2引数の指定について。単に 120×120 にリサイズしたいだけならそのように指定すればいいけど、これだとアスペクト比が保存されずに正方形のサムネイルができてしまう。アスペクト比を保存するには、幅か高さのうち小さいほうに 0 を指定してやればいいんだけど、画像が横長なのか縦長なのかで場合分けをする必要があった。

もう1つは、場合分けをするために元画像の幅と高さが必要なので、image.DecodeConfig 関数で取得している。けど、その後、そのまま画像のデコードをしようと image.Decode 関数を呼び出してもエラーになってしまうこと。原因は、image.DecodeConfig 関数でファイルの途中まで読み込んでいるので、そのままだと image.Decode 関数はファイルの途中から読み込むことになって正常にデコードできない、ということだと思う。
そこで (*File) Seek 関数で読み込み位置をファイルの先頭に戻している。この関数は引数を2つとり、1つ目は一のオフセット、2つ目はそのオフセットをどこからにするかの指定。0 だとファイルの先頭から、1 だと現在位置から、2 だとファイルの終わりからになる。
というわけで、Seek(0, 0) として読み込み位置をファイルの先頭に戻してから image.Decode を呼び出して成功した。

一応実行例。

^o^ > go run img_resize.go sample.jpg resized.jpg

横長の画像も、縦長の画像もうまくサムネイルができた。

select

select 文を使うと、複数のゴルーチンとの通信を選択的に処理することができる。
次の例では、main 関数で起動された3つのゴルーチンからの通信を for ループの中で select 文を使って待ち受けている。もし、どれかのゴルーチンから通信を受信すれば、受信したデータを出力して次のループに移る。どれからも受信できなければ default 節が実行されて "None" を出力し、250 ミリ秒待った後に次のループに移る。
各ゴルーチンは終了するときに専用のチャネル quit を使ってデータ(この例では 0 を使っているけど何でもいい。単に終了の通知をするだけなので。)を送ってくるので、全部のゴルーチンが終了したら for ループも終了する。

^o^ > go run channel_select.go
6
40
0.8
None
0.7
None
5
0.6
None
0.5
30
None
4
0.4
None
0.3
None
20
3
0.2
None
0.1
None
2
None
10
None
1
None
None

うまくいったようだ。

チャネルを使ってデータ交換

これまで、チャネルを使った例をいくつか見てきたけど、今回はもうちょっと意味のあるデータを通信してみる。

ゴルーチンで起動される関数 sendColor は送信用のチャネル ch に、色名(文字列)と返信用のチャネルをまとめた Req 構造体を送信する。で、それを n 回繰り返したら chnil を送って終了する。
receiveColor はチャネル ch から Req が送られてきたら、その色名を出力してから返信用のチャネル Req.Reply0 を送信する。これは処理が完了したことを知らせるだけなので、0 でなくても int なら何でもいい。で、それを n がゼロになるまで繰り返す。この n は起動されたゴルーチンの数だ。

では、実行例。

^o^ > go run channel_color.go
blue
red
green
blue
red
green
blue
red
green
blue
red
green
blue
red
green
blue
red
green
red
green
red

Go言語で画像フォーマットのチェック

たまたま見つけたので、メモしておく。

 cf. Go言語で画像ファイルか確認してみる – Qiita

画像を扱うには image パッケージを利用する。が、具体的なフォーマットを扱うにはそれぞれに対応したパッケージの import が必要。直接には使わないけど image パッケージの関数で内部的に使われる。注意しなきゃいけないのは、普通に import すると、インポートしてるのに使ってないぞエラーが出ること。そこで下のコードのように、パッケージ名の前に _ をつけている。

それから、標準で対応しているのは、JPEG, PNG, GIF のみ。ほかに BMP と TIFF が外部のパッケージを導入すれば対応できる。こんなふうに。

^o^ > go get golang.org/x/image/bmp

^o^ > go get golang.org/x/image/tiff

で、実行例。

^o^ > go run imgcheck.go sample.jpg
jpeg

^o^ > go run imgcheck.go sample.png
png

^o^ > go run imgcheck.go sample.bmp
bmp

さくらVPSのRailsアプリをバージョンアップ

さくらの VPS で動かしている Rails アプリをバージョンアップした、その記録。
今回のバージョンアップでは、Rails のバージョンアップしないけど、データベースの変更をするので若干緊張しながらやった。

まずは、アプリを停止する。
で、万が一のためにデータベースのバックアップ。

[takatoh@tk2-254-36564 ~]$ mysqldump -u lathercraft -p lathercraft_production > lcp-20180502.sql

次に、アプリのディレクトリに移動して git fetchgit merge origin/master

[takatoh@tk2-254-36564 ~]$ cd /var/www/lathercraft
[takatoh@tk2-254-36564 lathercraft]$ sudo git fetch
Password: 
remote: Counting objects: 126, done.
remote: Compressing objects: 100% (126/126), done.
remote: Total 126 (delta 95), reused 0 (delta 0)
Receiving objects: 100% (126/126), 11.91 KiB | 6 KiB/s, done.
Resolving deltas: 100% (95/95), completed with 21 local objects.
From https://bitbucket.org/takatoh/lathercraft
   14f1281..7aecdc3  master     -> origin/master
 * [new branch]      support-release-datetime -> origin/support-release-datetime
 * [new tag]         v2.0.1     -> v2.0.1
From https://bitbucket.org/takatoh/lathercraft
 * [new tag]         v2.0.0     -> v2.0.0
[takatoh@tk2-254-36564 lathercraft]$ sudo git merge origin/master
Auto-merging .gitignore
Auto-merging db/schema.rb
CONFLICT (content): Merge conflict in db/schema.rb
Automatic merge failed; fix conflicts and then commit the result.

あれ、db/schema.rb がコンフリクトした?なんでだ?
中身を見てみると、文字列のカラムに limit:255 がついている。このファイルはデータベースをマイグレートすれば更新されるはずなので、元に戻しておく。

[takatoh@tk2-254-36564 lathercraft]$ sudo vim db/schema.rb

修正が済んだら commit

[takatoh@tk2-254-36564 lathercraft]$ sudo git add db/schema.rb
[takatoh@tk2-254-36564 lathercraft]$ sudo git commit -m "merge branch origin/master."

さて、いよいよデータベースのマイグレーション。

[takatoh@tk2-254-36564 lathercraft]$ sudo -s
[root@tk2-254-36564 lathercraft]# export SECRET_KEY_BASE=bundle exec rake secret
[root@tk2-254-36564 lathercraft]# bundle exec rake db:migrate RAILS_ENV=production
DEPRECATION WARNING: The configuration option config.serve_static_assets has been renamed to config.serve_static_files to clarify its role (it merely enables serving everything in the public folder and is unrelated to the asset pipeline). The serve_static_assets alias will be removed in Rails 5.0. Please migrate your configuration files accordingly. (called from block in  at /var/www/lathercraft/config/environments/production.rb:23)
== 20180430070208 AddReleaseDatetimeToItems: migrating ========================
-- add_column(:items, :release_datetime, :datetime)
   -> 1.5631s
== 20180430070208 AddReleaseDatetimeToItems: migrated (1.5635s) ===============

なんか警告が出てるけどまあいいや。次に進もう。

アプリを production 環境で立ち上げてみる。

[root@tk2-254-36564 lathercraft]# export SECRET_KEY_BASE=bundle exec rake secret
[root@tk2-254-36564 lathercraft]# bundle exec rake db:migrate RAILS_ENV=production

無事、立ち上がった。けど、あるページではエラーになってしまった。開発環境で修正して本番環境に反映すると、直った。多分これで大丈夫。

最後に本番用に立ち上げて完了。

[追記]

マイグレーションのところで出た警告だけど、ググってみたら config.serve_static_assets という設定項目が config.serve_static_files に名前変更されている、ということが分かった。なので、その通りに修正して完了。

ext4のUSB外付けHDDにラベルをつける

Linux の ext4 ファイルシステムのディスクにも NTFS のボリュームラベルのようにラベル(名前)をつけられることを知った。

 cf. Linuxでストレージのラベルを確認・変更する

メインマシンの envelopes に繋がってる外付け HDD は NTFS なので、もうひとつの Ubuntu マシンである wplj の外付け HDD で試してみる。

まずはデバイスの確認。

takatoh@wplj $ lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda      8:0    0 465.8G  0 disk 
├─sda1   8:1    0   512M  0 part /boot/efi
├─sda2   8:2    0 461.4G  0 part /
└─sda3   8:3    0   3.9G  0 part [SWAP]
sdb      8:16   0   2.7T  0 disk 
└─sdb1   8:17   0   2.7T  0 part /media/opabinia
sdc      8:32   0   2.7T  0 disk 
└─sdc1   8:33   0   2.7T  0 part /media/aysheaia

sdb1 と sdc1 が USB の外付け HDD だ。df コマンドに -T オプションをつけるとファイルシステムの種類を表示してくれる。

takatoh@wplj $ df -T
Filesystem     Type      1K-blocks      Used  Available Use% Mounted on
udev           devtmpfs    1948676         0    1948676   0% /dev
tmpfs          tmpfs        393812     17240     376572   5% /run
/dev/sda2      ext4      476050488  17526428  434319056   4% /
tmpfs          tmpfs       1969056       216    1968840   1% /dev/shm
tmpfs          tmpfs          5120         4       5116   1% /run/lock
tmpfs          tmpfs       1969056         0    1969056   0% /sys/fs/cgroup
/dev/sdb1      ext4     2884152984 722813244 2014810080  27% /media/opabinia
/dev/sdc1      ext4     2884152984 981135372 1756487952  36% /media/aysheaia
/dev/sda1      vfat         523248      3496     519752   1% /boot/efi
tmpfs          tmpfs        393812        52     393760   1% /run/user/1000

ext4 ファイルシステムにラベルをつけるのは e2label コマンド。
e2label デバイス名で現在のラベルを表示し、e2label デバイス名 ラベルでラベルをつける(ないしは変更する)。

takatoh@wplj $ sudo e2label /dev/sdb1

ラベルがついていないので何も返ってこない。
じゃ、つけてみよう。

takatoh@wplj $ sudo e2label /dev/sdb1 OPABINIA
takatoh@wplj $ sudo e2label /dev/sdb1
OPABINIA

ついでに /dev/sdc1 にも。

takatoh@wplj $ sudo e2label /dev/sdc1 AYSHEAIA
takatoh@wplj $ sudo e2label /dev/sdc1
AYSHEAIA

無事、ラベルがついた。
ラベルは blkid コマンドでも確認できる。

takatoh@wplj $ sudo blkid
/dev/sda1: UUID="D17D-DA22" TYPE="vfat" PARTLABEL="EFI System Partition" PARTUUID="6fa2ce8f-fa84-4c97-8280-e58107f1f5f7"
/dev/sda2: UUID="55c35560-7f0d-42ea-895c-5522c3b0a757" TYPE="ext4" PARTUUID="2e419080-c527-4316-b546-f404b2974dc6"
/dev/sda3: UUID="fe038a20-aa4e-48d9-b98c-f8932b715370" TYPE="swap" PARTUUID="8383fce1-4445-419d-a75d-1e002ac985ef"
/dev/sdb1: LABEL="OPABINIA" UUID="cd8ae5c6-3931-4855-a60b-40f27b9bc518" TYPE="ext4" PARTUUID="76dd840a-3ce4-4092-9510-a127dd9f0297"
/dev/sdc1: LABEL="AYSHEAIA" UUID="4bd2ad4c-1acf-4468-847b-1dd0a0c474a2" TYPE="ext4" PARTUUID="33288db2-8046-449d-ae4f-03f23c3864b3"

repeated_combination

Ruby の Array#repeated_combination は重複を許す組み合わせを返すメソッドだ(厳密にいうとEnumerator を返す)。
次のブログ記事で見つけた。

 cf. 続:Haskell/Clojureでrepeated_combinationを実装してみる – Programmer’s Note

これを Go でやってみよう、ということで Haskell のコードを参考に作ってみた。

ところが、実行結果が Array#repeated_combination と合わない。
上のコードの実行結果はこう。

^o^ > go run repeated_combination.go
[[1 1 1] [1 1 2] [1 1 3] [1 2 1] [1 2 2] [1 2 3] [1 3 1] [1 3 2] [1 3 3] [2 1 1] [2 1 2] [2 1 3] [2 2 1] [2 2 2] [2 2 3] [2 3 1] [2 3 2] [2 3 3] [3 1 1] [3 1 2] [3 1 3] [3 2 1] [3 2 2] [3 2 3] [3 3 1] [3 3 2] [3 3 3]]

対して Array#repeated_combination はこう。

irb(main):001:0> [1,2,3].repeated_combination(3).to_a
=> [[1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 2, 2], [1, 2, 3], [1, 3, 3], [2, 2, 2], [2, 2, 3], [2, 3, 3], [3, 3, 3]]

順列ではなく組み合わせなので、Array#repeated_combination では例えば [1,1,2] と [1,2,1] と [2,1,1] を同じものとしているのに対して、Go のコードでは別のものとして数え上げてしまっている。
そこで改良を加えたのがこれだ。

実行結果。

^o^ > go run repeated_combination2.go
[[1 1 1] [1 1 2] [1 1 3] [1 2 2] [1 2 3] [1 3 3] [2 2 2] [2 2 3] [2 3 3] [3 3 3]]

今度はうまくいった。

コインの両替

最近見かけたブログの記事。

 cf. Haskell/Clojureで数学パズル:コインの両替 – Programmer’s Note

同じような問題はずっと前に Haskell でやったことがあるんだけど、今回は Go でやってみる。はじめに思い付いたのはリンク記事の後半に出てくる再帰を使ったほうなんだけど、まずはリンク記事と同じ順で for を使ったやり方で。

ああ、ネストした for 文が美しくない!!

で、こっちが再帰を使ったほう。

だいぶマシだけど else if がなあ。改めて Haskell のパターンマッチの美しいことを思い知る。