MyLittleNoteshttp://notes.kyu-mu.net/2023-11-08T13:06:00.779ZRaspberry Pi Camera v1 vs v2http://notes.kyu-mu.net/2016/05/06/raspberry_pi_camera_v1_vs_v22016-05-05T15:00:00.000Z<h1 id="raspberry-pi-camera-v1-vs-v2">Raspberry Pi Camera v1 vs v2</h1>
<p>Shots taken with Raspberry Pi Camera v1 (first image) and v2 (second image).<br><a href="http://notes.kyu-mu.net/2016/05/06/images/raspicam_v1.jpg"><img src="http://notes.kyu-mu.net/2016/05/06/images/raspicam_v1_small.jpg" alt="v1"></a> <a href="http://notes.kyu-mu.net/2016/05/06/images/raspicam_v2.jpg"><img src="http://notes.kyu-mu.net/2016/05/06/images/raspicam_v2_small.jpg" alt="v2"></a></p>
<p>Options: <code>raspistill -awb fluorescent</code><br>Board: Raspberry Pi 3 Model B</p>
Nao Iizukahttp://kyu-mu.net/iizuka@kyu-mu.netAndroid Marshmallow向け開発をEclipseで行うhttp://notes.kyu-mu.net/2015/11/04/android_marshmallow_on_eclipse2015-11-03T15:00:00.000Z<h1 id="android-marshmallow向け開発をeclipseで行う">Android Marshmallow向け開発をEclipseで行う</h1>
<p>Eclipse上のAndroid 4.x向けプロジェクトをLollipop(5.x)やMarshmallow(6.0)対応にしようとProject build targetを変えるとUnsupported major.minor version 51.0というエラーが出たり、ビルドがうまく行われなかったりしていた。</p>
<p>今まで使っていたEclipseを捨てて新しくインストールし、普通にADTを入れたら何の問題もなく動いた。どうやらADTを昔のバージョンからアップデートで使い続けているとうまく動かない様子。</p>
<p>はやくAndroid StudioでNDK正式サポートしてほしいなぁ…。</p>
Nao Iizukahttp://kyu-mu.net/iizuka@kyu-mu.netRaspberry Pi Model BとB+を比較するhttp://notes.kyu-mu.net/2014/08/21/raspberry_pi_b_vs_bplus2014-08-20T15:00:00.000Z<h1 id="raspberry-pi-model-bとbを比較する">Raspberry Pi Model BとB+を比較する</h1>
<p>Model B+を購入したので比較してみる。<br><a href="http://notes.kyu-mu.net/2014/08/21/images/raspi_b_vs_bplus.jpg"><img src="http://notes.kyu-mu.net/2014/08/21/images/raspi_b_vs_bplus_thumb.jpg" alt="Model BとB+の比較"></a><br>B+ではアダプタを使用しなくてもmicroSDカードを使えるようになった。microSDカードの出っ張りを押すとスチャッと簡単に外せる。これは賛否両論ありそうだけど、個人的には便利だと思う。カメラ入力端子の位置は少し左にずれた。</p>
<p>黄色いコンポジット出力端子は全く使っていなかったので、なくなってすっきり。オーディオ出力端子は上側から下側へ移った。</p>
<p>個人的に一番影響があったのは電源用のMicroUSB端子の位置。左から下へと向きが変わった。これのせいで、せっかく3Dプリンタで作った専用ケースを作り直さないといけないはめに…。それ以外の大きな変化はUSB端子が2つ増えたこととGPIOのピンが増えたこと。</p>
<p>下の写真は、右下のイーサネット端子を横から見たところ。<br><a href="http://notes.kyu-mu.net/2014/08/21/images/raspi_b_vs_bplus_usb.jpg"><img src="http://notes.kyu-mu.net/2014/08/21/images/raspi_b_vs_bplus_usb_thumb.jpg" alt="Model BとB+のUSB端子の出っ張り"></a><br>はみ出していたUSB端子もイーサネットと同じところまで引っ込んでくれたし、全体として好印象。基板の四隅も丸くなって怪我をしなくなった。消費電力も減ったらしいので、そのうち測定してみたい。</p>
Nao Iizukahttp://kyu-mu.net/iizuka@kyu-mu.netSymfony 2のコマンド内でTwigテンプレートをレンダリングするhttp://notes.kyu-mu.net/2014/07/11-4/symfony2_render_template_in_command2014-07-10T15:00:00.000Z<h1 id="symfony-2のコマンド内でtwigテンプレートをレンダリングする">Symfony 2のコマンド内でTwigテンプレートをレンダリングする</h1>
<p>ContainerAwareCommandを継承して以下のようにする。</p>
<pre><code>namespace Acme\DemoBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class RenderCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('demo:render')
->setDescription('テンプレートをレンダリングする')
->addArgument('name', InputArgument::REQUIRED, '名前');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument('name');
// テンプレートをレンダリングする
$templating = $this->getContainer()->get('templating');
$content = $templating->render(
'AcmeDemoBundle:Demo:greeting.txt.twig', array(
'name' => $name
)
);
$output->writeln($content);
return 0;
}
}
</code></pre>
Nao Iizukahttp://kyu-mu.net/iizuka@kyu-mu.netPHPでGDを使って画像を鮮明(シャープ)にするhttp://notes.kyu-mu.net/2014/07/11-3/php_sharpen_image_with_gd2014-07-10T15:00:00.000Z<h1 id="phpでgdを使って画像を鮮明(シャープ)にする">PHPでGDを使って画像を鮮明(シャープ)にする</h1>
<p>PHP 5.1.0以上で使える<code>imageconvolution</code>関数を使って画像を鮮明にする。サムネイルなど小さな画像は多少鮮明にすると見やすくなる。</p>
<pre><code>// test.jpgを読み込む
$image = imagecreatefromjpeg('test.jpg');
if ($image !== false) {
$matrix = array(
array(0.0, -1.0, 0.0),
array(-1.0, 9.0, -1.0),
array(0.0, -1.0, 0.0)
);
$divisor = array_sum(array_map('array_sum', $matrix));
imageconvolution($image, $matrix, $divisor, 0);
// out.jpgに出力
imagejpeg($image, 'out.jpg');
imagedestroy($image);
}
</code></pre>
<p><code>$matrix</code>の真ん中の値(上の例では9.0)を小さくしていくとよりシャープになり、大きくしていくとシャープ加減が弱くなる。4.5以下はあまり実用的ではない感じ。個人的には9.0が好み。</p>
Nao Iizukahttp://kyu-mu.net/iizuka@kyu-mu.netZipArchiveを作成して中身のデータだけ取得するhttp://notes.kyu-mu.net/2014/07/11-2/php_create_zip_and_get_content2014-07-10T15:00:00.000Z<h1 id="ziparchiveを作成して中身のデータだけ取得する">ZipArchiveを作成して中身のデータだけ取得する</h1>
<p>PHPのZipArchiveは一旦ファイルシステム上にファイルを作成する必要がある。</p>
<pre><code>// 一時ファイルを作成
$filename = tempnam(sys_get_temp_dir(), 'example-prefix');
$zip = new ZipArchive();
$ret = $zip->open($filename, ZipArchive::CREATE);
if ($ret === true) {
// somedir/a.txtをa.txtという名前で追加
if ($zip->addFile("somedir/a.txt", "a.txt") === false) {
echo "error: failed to add a.txt\n";
}
// somedir/b.txtをb.txtという名前で追加
if ($zip->addFile("somedir/b.txt", "b.txt") === false) {
echo "error: failed to add b.txt\n";
}
$zip->close();
$zipContent = file_get_contents($filename);
unlink($filename); // 一時ファイルを削除
// $zipContentにはzipファイルのバイナリデータが入っている
} else {
echo "failed to open a zip file (code=$ret)\n";
}
</code></pre>
<p>一時ファイルをRAMディスク内に作成することもできるが、zipファイルのサイズが大きくなると容量が足りなくなる可能性があるので避けた方が無難。</p>
Nao Iizukahttp://kyu-mu.net/iizuka@kyu-mu.netSymfony 2でコントローラからコマンドを実行するhttp://notes.kyu-mu.net/2014/07/11/run_command_from_controller_in_symfony22014-07-10T15:00:00.000Z<h1 id="symfony-2でコントローラからコマンドを実行する">Symfony 2でコントローラからコマンドを実行する</h1>
<p>まずコマンドのクラスを作成する。</p>
<p>src/Acme/DemoBundle/Command/GreetCommand.php:</p>
<pre><code><?php
namespace Acme\DemoBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class GreetCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('demo:greet') // 何でもOK
->setDescription('挨拶する')
// 必須の引数
->addArgument('id', InputArgument::REQUIRED, 'ID')
// 引数を取る任意のオプション
->addOption('name', 'a',
InputOption::VALUE_REQUIRED, 'あなたの名前');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$id = $input->getArgument('id');
$name = $input->getOption('name');
if (!is_numeric($id)) {
$output->writeln("<error>IDを数字で指定してください。</error>");
return 1; // 終了コード 1
}
if ($name !== null) { // nameオプションが指定された
$msg = "${id}番の${name}さん、こんにちは。";
} else {
$msg = "${id}番の方、こんにちは。";
}
$output->writeln($msg);
return 0; // 終了コード 0
}
}
</code></pre>
<p>コマンドをsrc/Acme/DemoBundle/Resources/config/services.ymlにサービスとして追加する。</p>
<pre><code>services:
# 挨拶するコマンド
DemoGreetCommandService:
class: Acme\DemoBundle\Command\GreetCommand
calls:
- [setContainer, ["@service_container"] ]
</code></pre>
<p>services.xmlを使用している場合は以下のように記述する。</p>
<pre><code><services>
<service id="DemoGreetCommandService" class="Acme\DemoBundle\Command\GreetCommand">
<call method="setContainer">
<argument type="service" id="service_container" />
</call>
</service>
</services>
</code></pre>
<p>コントローラでは以下のようにして呼び出す。</p>
<pre><code>use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
...
public function greetAction()
{
$input = new ArgvInput(array(
// 引数を並べていく。最初の引数は何でもよい。
'demo:greet', 4, '--name', '三郎',
));
$output = new ConsoleOutput();
$greetCommand = $this->get('DemoGreetCommandService');
$exitCode = $greetCommand->run($input, $output);
if ($exitCode !== 0) { // コマンドが異常終了した
return new Response('コマンド実行失敗');
}
return new Response('コマンドを実行しました');
}
</code></pre>
<p>上のようにConsoleOutputを使うと、コマンドの出力はターミナルに出力される。出力を受け取りたい場合は、<a href="https://github.com/predakanga/CronBundle/blob/master/Command/MemoryWriter.php">MemoryWriter</a>を以下のように使う。<code>$output->getOutput()</code>でコマンドの出力を受け取れる。</p>
<pre><code>public function greetAction()
{
$input = new ArgvInput(array(
// 引数を並べていく。最初の引数は何でもよい。
'demo:greet', 4, '--name', '三郎',
));
$output = new MemoryWriter();
$greetCommand = $this->get('DemoGreetCommandService');
$exitCode = $greetCommand->run($input, $output);
if ($exitCode !== 0) { // コマンドが異常終了した
return new Response('コマンド実行失敗:' . $output->getOutput());
}
return new Response( $output->getOutput() );
}
</code></pre>
<p>また、以下のようにしてコマンド内から別のコマンドを呼び出すこともできる。</p>
<p>src/Acme/DemoBundle/Command/ByeCommand.php:</p>
<pre><code><?php
namespace Acme\DemoBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\ArgvInput;
class ByeCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('demo:bye')
->setDescription('別れの挨拶をする')
->addArgument('id', InputArgument::REQUIRED, 'ID');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$id = $input->getArgument('id');
$child_input = new ArgvInput(array(
'demo:greet', $id
));
$child_output = new MemoryWriter();
$greetCommand = $this->getContainer()
->get('DemoGreetCommandService');
$exitCode = $greetCommand->run($child_input, $child_output);
if ($exitCode !== 0) {
$output->writeln('<error>コマンド実行失敗:'
. $child_output->getOutput() . '</error>');
return 1;
}
$output->writeln( $child_output->getOutput() );
$output->writeln('またね。');
return 0;
}
}
// https://github.com/predakanga/CronBundle/blob/master/Command/MemoryWriter.php
use Symfony\Component\Console\Output\Output;
/**
* MemoryWriter implements OutputInterface, writing to an internal buffer
*/
class MemoryWriter extends Output {
protected $backingStore = "";
public function __construct($verbosity = self::VERBOSITY_NORMAL) {
parent::__construct($verbosity);
}
public function doWrite($message, $newline) {
$this->backingStore .= $message;
if($newline) {
$this->backingStore .= "\n";
}
}
public function getOutput() {
return $this->backingStore;
}
public function clear() {
$this->backingStore = "";
}
}
</code></pre>
Nao Iizukahttp://kyu-mu.net/iizuka@kyu-mu.netSymfony 2でCompassを使うhttp://notes.kyu-mu.net/2014/07/10/use_compass_with_symfony22014-07-09T15:00:00.000Z<h1 id="symfony-2でcompassを使う">Symfony 2でCompassを使う</h1>
<p>compassをインストールする(入っていない場合)。</p>
<pre><code>$ gem install compass
</code></pre>
<p>Symfonyプロジェクトのapp/config/config.ymlで<code>assetic.bundles</code>にバンドル名を入れてAsseticを有効にする。ここでは例としてAcmeDemoBundleとする。</p>
<pre><code>assetic:
bundles: [ AcmeDemoBundle ]
</code></pre>
<p>さらに<code>assetic.filters.compass.apply_to</code>で.sassと.scssの拡張子を指定する。こうすると、.sassと.scssの拡張子に対してcompassが自動的に実行されるようになる。</p>
<pre><code>assetic:
bundles: [ AcmeDemoBundle ]
filters:
compass:
apply_to: "\.s[ac]ss$"
</code></pre>
<p>スタイルシートを作成する。ここでは例としてsite.scssとする。</p>
<p>src/Acme/DemoBundle/Resources/public/css/site.scss:</p>
<pre><code>table {
border-collapse: collapse;
thead td {
padding: 5px;
background-color: #f0f0f0;
}
tbody td {
padding: 10px;
border-bottom: 1px solid #ccc;
}
}
</code></pre>
<p>Twigテンプレートに以下のように埋め込んで完了。</p>
<pre><code>{% stylesheets "@AcmeDemoBundle/Resources/public/css/site.scss" %}
<link rel="stylesheet" href="{{ asset_url }}">
{% endstylesheets %}
</code></pre>
<p>/usr/bin/rubyが存在しかつrbenvを使用している場合などで、<code>rbenv: not found</code>や<code>no Ruby script found in input (LoadError)</code>といった500エラーが出ることがある。このような場合は、rubyやcompassコマンドへのパスをconfig.ymlまたはconfig_dev.ymlなどで指定する。</p>
<pre><code>assetic:
bundles: [ AcmeDemoBundle ]
ruby: /home/user/.rbenv/versions/1.9.3-p194/bin/ruby
filters:
compass:
apply_to: "\.s[ac]ss$"
bin: /home/user/.rbenv/versions/1.9.3-p194/bin/compass
</code></pre>
Nao Iizukahttp://kyu-mu.net/iizuka@kyu-mu.netcomposer updateとcomposer installの違いhttp://notes.kyu-mu.net/2014/07/10/php_composer_update_vs_install2014-07-09T15:00:00.000Z<h1 id="composer-updateとcomposer-installの違い">composer updateとcomposer installの違い</h1>
<p>PHPの依存管理ツール<a href="https://getcomposer.org/">Composer</a>について。</p>
<h2 id="composer-update">composer update</h2>
<p>composer.jsonをもとに各ライブラリを最新版に更新し、composer.lockを生成する。composer.lockにはインストールした各ライブラリのバージョン情報が記載される。</p>
<h2 id="composer-install">composer install</h2>
<p>各ライブラリについて、composer.lockに記載されているバージョンをインストールする。composer.lockが存在しない場合は<code>composer update</code>と同じ効果。</p>
<h2 id="使い分け">使い分け</h2>
<p><code>composer update</code>はライブラリを最新版に更新するが、<code>composer install</code>はcomposer.lockに記載された特定のバージョンをインストールする。</p>
<p>ライブラリをいきなり最新版に更新すると不具合が出る場合があるので、まず開発環境で<code>composer update</code>を実行してから、システムに問題が出ていないことを確認する。問題なければ、開発環境で生成されたcomposer.lockを本番環境にコピーして、本番環境では<code>composer install</code>だけを実行する。本番環境で<code>composer update</code>を実行してしまうと、ライブラリが最新版になって思わぬ不具合が出ることがある。</p>
<p>また、composer.lockを開発チーム内で共有すれば、各メンバーが使っているライブラリのバージョンを完全に揃えることができる。</p>
Nao Iizukahttp://kyu-mu.net/iizuka@kyu-mu.netBitter blog enginehttp://notes.kyu-mu.net/2013/05/29/introducing_bitter_blog_engine2013-05-28T15:00:00.000Z<h1 id="bitter-blog-engine">Bitter blog engine</h1>
<p><span style="font-size:80%"><a href="http://notes.kyu-mu.net/2013/05/28/blog_engine_bitter">Japanese version(日本語の説明はこちら)</a></span></p>
<p><em>Minimal blog engine for Git and Markdown enthusiasts</em></p>
<p>I made a new blog engine "Bitter". The main features are:</p>
<ul>
<li>Publish article by "git push"</li>
<li>Write article in Markdown</li>
<li>Suitable for text content</li>
<li>Pure Node.js server</li>
<li>Simple code base that you can easily hack on</li>
</ul>
<h2 id="source-code">Source code</h2>
<p>The repository is on <a href="https://github.com/iizukanao/bitter">GitHub</a>.</p>
<h2 id="install">Install</h2>
<p>On the server side, do as follows. Please replace "blogdir" with your desired directory.</p>
<pre><code>$ npm install -g bitter
$ mkdir blogdir
$ cd blogdir
$ bitter setup
</code></pre>
<p>After setup completed, run the server. Specify listen port with environment variable PORT.</p>
<pre><code>$ PORT=1341 bitter server
</code></pre>
<p>If you're going to use with nginx or Apache, configure them to use reverse proxy.</p>
<p>On the local machine, clone blogdir/notes.git.</p>
<pre><code>$ git clone user@host:blogdir/notes.git
</code></pre>
<h2 id="to-post-an-entry">To post an entry</h2>
<p>In the cloned repository on the local machine, write an article in Markdown, then save it as <code>year/month/date-slug.md</code>. Do <code>git push</code> to publish it.</p>
<pre><code>$ mkdir -p 2013/05
$ echo "# Test\n\nHello World" > 2013/05/27-test.md
$ git add .
$ git commit -m "add test entry"
$ git push origin master
</code></pre>
<p>Commit message has no effect on Bitter.</p>
<h2 id="to-edit-an-existing-entry">To edit an existing entry</h2>
<pre><code>$ vim 2013/05/27-test.md
$ git add -u
$ git commit -m update
$ git push origin master
</code></pre>
<h2 id="to-delete-an-entry">To delete an entry</h2>
<pre><code>$ git rm 2013/05/27-test.md
$ git commit -m delete
$ git push origin master
</code></pre>
<h2 id="to-embed-static-files">To embed static files</h2>
<p>Put your static files under the directory that article resides. Those files can be referred to by relative path. For example, to embed 2013/05/images/winter.jpg, write following code in 2013/05/27-test.md.</p>
<pre><code>![Winter photo](images/winter.jpg)
</code></pre>
<p>If you put static files under "public" directory, those files can be referred to by absolute path. For example, to embed public/images/spring.jpg:</p>
<pre><code>![Spring photo](/images/spring.jpg)
</code></pre>
Nao Iizukahttp://kyu-mu.net/iizuka@kyu-mu.netブログエンジンBitterhttp://notes.kyu-mu.net/2013/05/28/blog_engine_bitter2013-05-27T15:00:00.000Z<h2 id="ブログエンジンbitter">ブログエンジンBitter</h2>
<p>自分のブログ用に、新しいブログエンジン「Bitter」を作りました。このブログもBitterで動いています。</p>
<h3 id="特徴">特徴</h3>
<ul>
<li>ローカルからgit pushで投稿</li>
<li>Markdownで記述</li>
<li>サーバサイドはNode.jsで、Webサーバも兼ねる</li>
<li>文章主体のブログ向け</li>
<li>改造しやすいシンプルな構成</li>
</ul>
<h3 id="ソースコード">ソースコード</h3>
<p><a href="https://github.com/iizukanao/bitter">GitHub</a>にあります。</p>
<h3 id="インストール">インストール</h3>
<p>サーバー側で下記のコマンドを入力します。blogdirは、ファイルを置くディレクトリとして読み替えてください。</p>
<pre><code>$ npm install -g bitter
$ mkdir blogdir
$ cd blogdir
$ bitter setup
</code></pre>
<p>次のコマンドでサーバを起動します。環境変数PORTでポート番号を指定します。</p>
<pre><code>$ PORT=1341 bitter server
</code></pre>
<p>nginxやApacheと組み合わせて使う場合はリバースプロキシを使ってください。</p>
<p>ローカル側では、記事ディレクトリをcloneします。</p>
<pre><code>$ git clone user@host:blogdir/notes.git
</code></pre>
<h3 id="記事を投稿する">記事を投稿する</h3>
<p>ローカルでcloneした記事ディレクトリ内で、<code>年/月/日-slug.md</code>のようなファイル名でMarkdownを作成し、pushすると公開されます。</p>
<pre><code>$ mkdir -p 2013/05
$ echo "# Test\n\nHello World" > 2013/05/27-test.md
$ git add .
$ git commit -m "add test entry" # メッセージは何でもOK
$ git push origin master
</code></pre>
<h3 id="既存の記事を編集する">既存の記事を編集する</h3>
<pre><code>$ vim 2013/05/27-test.md
$ git add -u
$ git commit -m update # メッセージは何でもOK
$ git push origin master
</code></pre>
<h3 id="記事を削除する">記事を削除する</h3>
<pre><code>$ git rm 2013/05/27-test.md
$ git commit -m delete # メッセージは何でもOK
$ git push origin master
</code></pre>
<h3 id="画像を埋め込む">画像を埋め込む</h3>
<p>記事と同じディレクトリ以下に画像ファイルを置くと、相対パスで参照できます。例えば、2013/05/images/winter.jpgを埋め込むには、2013/05/27-test.mdに次のように書きます。</p>
<pre><code>![Winter photo](images/winter.jpg)
</code></pre>
<p>一方、publicディレクトリ以下にファイルを置くと、絶対パスで参照できます。例えば、public/images/spring.jpgを埋め込むには次のように書きます。</p>
<pre><code>![Spring photo](/images/spring.jpg)
</code></pre>
Nao Iizukahttp://kyu-mu.net/iizuka@kyu-mu.netMarkdown Examplehttp://notes.kyu-mu.net/2013/05/27/markdown_example2013-05-26T15:00:00.000Z<h1 id="markdown-example">Markdown Example</h1>
<p>This blog is powered by <a href="http://notes.kyu-mu.net/2013/05/29/introducing_bitter_blog_engine">Bitter</a>.<br>You can use <a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet">Github Flavored Markdown</a>.</p>
<h2 id="heading">Heading</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<ul>
<li>Item 1<ul>
<li>Item 1-1</li>
<li>Item 1-2</li>
</ul>
</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<p>Here is the code.</p>
<pre><code>console.log "hello"
setTimeout ->
console.log "world"
, 1000
</code></pre>
<blockquote>
<p>This is a blockquote.</p>
</blockquote>
<hr>
<h3 id="tables">Tables</h3>
<table>
<thead>
<tr>
<th>Item</th>
<th align="right">Price</th>
<th align="right">Quantity</th>
</tr>
</thead>
<tbody><tr>
<td>Coffee</td>
<td align="right">$1.49</td>
<td align="right">2</td>
</tr>
<tr>
<td>Tea</td>
<td align="right">$1.39</td>
<td align="right">1</td>
</tr>
<tr>
<td>Cake</td>
<td align="right">$2.99</td>
<td align="right">3</td>
</tr>
</tbody></table>
<h3 id="images">Images</h3>
<p>You can embed images like this.</p>
<p><img src="http://notes.kyu-mu.net/2013/05/27/images/winter.jpg" alt="Winter"></p>
<p>Using this code:</p>
<pre><code>![Winter](images/winter.jpg)
</code></pre>
Nao Iizukahttp://kyu-mu.net/iizuka@kyu-mu.netGmail IMAP with OAuthhttp://notes.kyu-mu.net/2010/10/21/gmail-imap-with-oauth2010-10-20T15:00:00.000Z<h1 id="gmail-imap-with-oauth">Gmail IMAP with OAuth</h1>
<p>Gmail provides <a href="http://code.google.com/apis/gmail/oauth/">IMAP OAuth interface</a>. With this API, developer can access authorized user’s mailbox without password. While OAuth mechanism is complicated but its procedure is quite simple, even you don’t have to register your app to Google.</p>
<p>Authorization procedure can be breaked down into five steps:</p>
<ol>
<li>Get request token from Google</li>
<li>Redirect user to Google account auth page with the request token</li>
<li>After user allows the app, user is redirected back to our site with verifier code.</li>
<li>Request access token to Google with the verifier code. Then authorization is completed.</li>
<li>You can access the user’s mailbox by using standard IMAP protocol with the access token. Access token never expires, except when the user revoked access to the app.</li>
</ol>
<p>Followings are example code by Perl, but what we send and receive are same to other languages.</p>
<pre><code class="lang-perl">#!/usr/bin/perl
use strict;
use warnings;
use Net::OAuth;
use LWP::UserAgent;
use HTTP::Request::Common;
use URI;
$Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A;
my $ua = LWP::UserAgent->new;
my $request = Net::OAuth->request('request token')->new(
consumer_key => 'anonymous',
consumer_secret => 'anonymous',
request_url => 'https://www.google.com/accounts/OAuthGetRequestToken',
request_method => 'GET',
signature_method => 'HMAC-SHA1',
timestamp => time,
nonce => int( rand(2**64-1) ), # random string
callback => 'http://yourserver.example.com/callback', # change to your URL
extra_params => {
scope => 'https://mail.google.com/',
},
);
$request->sign;
# Get request token
my $res = $ua->request( GET $request->to_url );
my $uri = URI->new;
# Response is like this:
# oauth_token=xxxx&oauth_token_secret=xxxx&oauth_callback_confirmed=true
# Split into key and value
$uri->query( $res->decoded_content );
my $tokens = $uri->query_form_hash;
my $request_token = $tokens->{oauth_token};
my $request_token_secret = $tokens->{oauth_token_secret};
# Redirect user to this URL
my $redirect_url = 'https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token='.$tokens->{oauth_token}."¥n";
# After user granted access, the user will be redirected to the callback URL with
# oauth_verifier and oauth_token in query string:
# http://yourserver.example.com/callback?oauth_verifier=xxxx&oauth_token=xxxx
# Get oauth_verifier and oauth_token from URL in some way
my $verifier = 'xxxx';
# Then request the access token with the verifier
$request = Net::OAuth->request('access token')->new(
consumer_key => 'anonymous',
consumer_secret => 'anonymous',
request_url => 'https://www.google.com/accounts/OAuthGetAccessToken',
request_method => 'GET',
signature_method => 'HMAC-SHA1',
timestamp => time,
nonce => int( rand(2**64-1) ), # random string
token => $request_token,
token_secret => $request_token_secret,
verifier => $verifier,
);
$request->sign;
$res = $ua->request( GET $request->to_url );
$uri = URI->new;
# response is like this:
# oauth_token=xxxx&oauth_token_secret=xxxx
# Split into key and value
$uri->query( $res->decoded_content );
my $access_tokens = $uri->query_form_hash;
# This is what you need
my $access_token = $access_tokens->{oauth_token};
my $access_token_secret = $access_tokens->{oauth_token_secret};
</code></pre>
<p>Now you can access the mailbox in this way:</p>
<pre><code class="lang-perl">#!/usr/bin/perl
use strict;
use warnings;
use Mail::IMAPClient;
use Net::OAuth;
use MIME::Base64;
# Gmail address
my $email = 'example@gmail.com';
# Access tokens that you got before
my $access_token = 'xxxx';
my $access_token_secret = 'xxxx';
my $url = 'https://mail.google.com/mail/b/'.$email.'/imap/';
my $request = Net::OAuth->request('access token')->new(
consumer_key => 'anonymous',
consumer_secret => 'anonymous',
request_url => $url,
request_method => 'GET',
signature_method => 'HMAC-SHA1',
timestamp => time,
nonce => int( rand( 2 ** 64 - 1 ) ), # random string
token => $access_token,
token_secret => $access_token_secret,
);
$request->sign;
my $auth_header = $request->to_authorization_header;
$auth_header =‾ s/^OAuth¥s*//;
my $xoauth_string = "GET $url $auth_header";
my $xoauth_base64 = encode_base64($xoauth_string, '');
my $imap = Mail::IMAPClient->new(
Server => 'imap.gmail.com',
User => $email,
Port => 993,
Ssl => 1,
Uid => 1,
Debug => 1,
);
die "Can't connect to IMAP server: $@" unless $imap;
$imap->authenticate('XOAUTH', sub { $xoauth_base64 } )
or die "Could not authenticate: " . $imap->LastError;
$imap->select('INBOX');
</code></pre>
Nao Iizukahttp://kyu-mu.net/iizuka@kyu-mu.net