2009年10月31日土曜日

libHaru on iPhone: Objective-Cでエラーハンドリング



前回記載できなかったエラーハンドリングについて軽くさわってみました。


■libHaruのエラーハンドリング
http://libharu.org/wiki/Documentation/Error_handlingに詳しく書いてあります。要するに、一番最初のPDFドキュメントを生成するところでエラーハンドラ関数を渡したらあとはエラーが発生するたびにその関数が呼び出されるみたいです。
// まずはヘッダファイルの中で
// ユーザーデータ用の構造体の宣言と、エラーハンドラ関数のプロトタイプ宣言をしておく

typedef struct _PDFService_userData {
HPDF_Doc pdf;
PDFService *service;
NSString *filePath;
} PDFService_userData;

// エラーハンドラ関数のシグネチャは常にこうでなければならない
// 関数名は自由に決めて良いが、引数と返り値は決められている
void PDFService_errorHandler(HPDF_STATUS error_no,
HPDF_STATUS detail_no,
void *user_data);
// 注意点として、エラーハンドラ関数はHPDF_Newより前の行で実装しておかなければならない
// (プロトタイプ宣言しておいてもだめみたいです)
void PDFService_errorHandler(HPDF_STATUS error_no,
HPDF_STATUS detail_no,
void *user_data) {
// ユーザーデータを取得する
// C言語の構造体からでもObjective-Cのクラスが取り出せますよ
PDFService_userData *userData = (PDFService_userData *)user_data;
HPDF_Doc pdf = userData->pdf;
PDFService *service = userData->service;
NSString *filePath = userData->filePath;

// エラーハンドリング後、PDF関連の処理を続行したい場合は
// このHPDF_ResetError関数を呼び出す必要がある。
// これを呼び出さないと後続の処理がうまくいかない。
HPDF_ResetError(pdf);

// 逆にエラーハンドリング後、PDF関連の処理をすべて中断したい場合には
// HPDF_Free関数を使ってpdfオブジェクトを解放するとよい。こうすると、以後の
// HPDF関連の関数はすべて何もしないで終了してくれる。ただし、同じpdfオブジェクトを
// 2回HPDF_Freeしてしまうとエラーになるため、HPDF_HasDocを使って制御すること
HPDF_Free(pdf);
}

// あとはPDFドキュメント生成時に関数とユーザーデータを引数として渡す
int main(int argc, char** args) {
PDFService_userData userData;
HPDF_Doc pdf = HPDF_New(PDFService_defaultErrorHandler, &userData);
userData.pdf = pdf;
userData.filePath = @"/var/tmp/hogehoge.pdf";
// 以下省略
}


■CとObjective-Cをつなげる
C言語的にはこれでよいのですが、Objective-C、ひいてはCocoa Touchフレームワーク的にはこういうエラー処理はdelegateにしてしまったほうが便利です。たとえば、PDFファイルを生成するためのObjective-CクラスPDFServiceに、PDFServiceDelegateを持たせて運用してみましょう。libHaruのエラーハンドラ関数を以下のように作ってみます。
void PDFService_defaultErrorHandler(HPDF_STATUS   error_no,
HPDF_STATUS detail_no,
void *user_data)
{
// ユーザーデータを展開
PDFService_userData *userData = (PDFService_userData *)user_data;
HPDF_Doc pdf = userData->pdf;
PDFService *service = userData->service;
NSString *filePath = userData->filePath;

// 以後のPDF作成処理をすべてストップする
HPDF_Free(pdf);

// Delegate呼び出し
if (service.delegate) {
[service.delegate service:service
didFailedCreatingPDFFile:filePath
errorNo:error_no
detailNo:detail_no];
}
}
あとはこのDelegateを適当なViewControllerなどに準拠させて、
#pragma mark -
#pragma mark delegate method
- (void)service:(PDFService *)service
didFailedCreatingPDFFile:(NSString *)filePath
errorNo:(HPDF_STATUS)errorNo
detailNo:(HPDF_STATUS)detailNo
{
NSString *message = [NSString stringWithFormat:@"Couldn't create a PDF file at %@\n errorNo:0x%04x detalNo:0x%04x",
filePath,
errorNo,
detailNo];
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"PDF creation error"
message:message
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] autorelease];
[alert show];
}
とすると、冒頭のイメージのようになります。

2009年10月24日土曜日

libHaruを使ってiPhoneアプリからPDFを作成する

ある日突然、iPhoneからPDFファイルを作ったら面白そうだと思ったので、早速試してみることにしました。既に先駆者の方がいらっしゃるかと思ったのですが、調べてもそれらしい資料が無いので自力でなんとかしてみることにしました。需要はあまり無いかと思いますが、何かのお役に立てば幸いです。


■PDFファイルを生成する方法
まずはPDFファイルを生成する方法を知らなければ話になりません。ということで、調査しました。
  • 一からライブラリを自作する
    jspdf(http://code.google.com/p/jspdf/)などという漢気あふれるライブラリがあるのですから、やってやれないことはない!と思い、さっそくPDFの仕様書(http://www.adobe.com/devnet/pdf/)をAdobe社からダウンロードしてきたのですが、31MB, 500ページ以上あります。なにこれこわい。
  • Adobe PDF Library(http://www.adobe.com/devnet/pdf/library/ http://www.est.co.jp/pdfl/index.html
    本家のライブラリです。日本ではイースト社がライセンスしてるみたいですが、どう見ても有償です。チェンジお願いします。
  • libHaru(http://libharu.org/wiki/Main_Page
    こうして散々探した挙句ついに見つけた至高のPDFライブラリがこちら。C言語で書かれており、iPhoneでもバッチリ動きそうです!もちろんオープンソース!素晴らしい!
ということで、今回はlibHaruを使ってみようと思います。


■libHaruをXcodeでビルドする
1.libHaruのソースコードをダウンロードしてXcodeプロジェクトに追加
最新のソースコードをhttp://libharu.org/wiki/Downloadsからダウンロードしてきて解凍します。このまま自分のMacで使えるようにするだけなら. configureしてmakeしてmake installすれば一発なのですが、あいにくそれではiPhoneで動かすことが出来ません。
includeディレクトリにヘッダファイルが、srcディレクトリにCソースコードが入ってますので、これら全部を自分のXcodeプロジェクトにコピーして取りこみます。


2.libpngのソースコードをダウンロードしてXcodeプロジェクトに追加
libHaruを用いてPDFに画像を出力したいときには、別途libpngが必要になります。そこで、libpngも一緒にビルドすることにします。libpngのソースコードはhttp://www.libpng.org/pub/png/libpng.htmlから入手できますので、これをダウンロードしてきてXcodeに追加してビルドすればよいのですが、ここではもっと簡単で確実に動く方法をご紹介します。そう、「既にlibpngをビルドして使っている他のiPhoneプロジェクトからパクる」です!(もちろん、きちんとライセンスに違反していないことを確認しましょう)
おあつらえ向きなことに、cocos2d for iPhoneの0.8.1以降(http://code.google.com/p/cocos2d-iphone/source/browse/#svn/trunk)では内部的にlibpngを使用しています。これを真似しない手はありません。さっそくcocos2d for iPhoneの最新ソースを取得し、Xcodeでプロジェクトを開いて、Support/external/libpng以下のソースをコピーして自分のプロジェクトに追加しましょう。


3.libHaruをconfigureする
. configureで動けばいいのですが、多分. configureしても今の自分のMacの環境に合わせてConfigureされてしまい、iPhoneでは動かないだろうと思ったので、自分の手でconfigureすることにしました。といっても、直すのは一箇所だけです。includeディレクトリに入っていたhpdf_config.h.inファイルを以下のように修正します。
/* include/hpdf_config.h.in.  Generated from configure.in by autoheader.  */

/* Define to 1 if you have the header file. */
#undef HAVE_DLFCN_H
#define HAVE_DLFCN_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_INTTYPES_H
#define HAVE_INTTYPES_H 1

/* Define to 1 if you have the `png' library (-lpng). */
#undef HAVE_LIBPNG
#define HAVE_LIBPNG 1

/* Define to 1 if you have the `z' library (-lz). */
#undef HAVE_LIBZ
#define HAVE_LIBZ 1

/* Define to 1 if you have the header file. */
#undef HAVE_MEMORY_H
#define HAVE_MEMORY_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_STDINT_H
#define HAVE_STDINT_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_STDLIB_H
#define HAVE_STDLIB_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_STRINGS_H
#define HAVE_STRINGS_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_STRING_H
#define HAVE_STRING_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_SYS_STAT_H
#define HAVE_SYS_STAT_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_SYS_TYPES_H
#define HAVE_SYS_TYPES_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_UNISTD_H
#define HAVE_UNISTD_H 1

/* debug build */
#undef HPDF_DEBUG
#ifdef DEBUG
#define HPDF_DEBUG 1
#endif

/* debug trace enabled */
#undef HPDF_DEBUG_TRACE
#ifdef DEBUG
#define HPDF_DEBUG_TRACE 1
#endif

/* libpng is not available */
#undef HPDF_NOPNGLIB

/* zlib is not available */
#undef HPDF_NOZLIB

/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT

/* Define to the full name of this package. */
#undef PACKAGE_NAME

/* Define to the full name and version of this package. */
#undef PACKAGE_STRING

/* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME

/* Define to the version of this package. */
#undef PACKAGE_VERSION

/* Define to 1 if you have the ANSI C header files. */
#undef STDC_HEADERS
#define STDC_HEADERS 1

/* Define to `unsigned int' if does not define. */
/* #undef size_t */
書き換えたら、ファイル名をhpdf_config.hに変更します。


4.Xcodeから新規ビルドターゲットを作成する
ソースコードの準備が出来たら、次はソースコードをビルドしてlibharu.aとlibpng.aを生成するためのビルドターゲットを新規に作成します。ビルドターゲットは使い方を覚えると非常に便利です。




最初にlibpngのビルドターゲットを作ります。libpngのビルドターゲットは、2.でご紹介したとおりcocos2d for iPhoneの最新ソースのXcodeプロジェクト内に既に用意されていますので、これを真似して作るとよいです。ただし、libpngをビルドする際にzlib.dylibが必要になりますので、自分のプロジェクトにzlib.dylibを追加するようにしておいてください。最初からiPhone SDKの一部として用意されています。



続いてlibHaruのビルドターゲットを作成します。こんな感じです。



最後に、libharu.aを自分のiPhoneプロジェクトの依存ライブラリに追加します。


5.レッツコンバイン!
これでビルドする準備が整いましたので、いよいよビルドします。
緊張の一瞬!Cmd+Bをプログラムドライブ!!



できました!!!!


■Hello, libHaru!
無事にビルドが完了いたしましたので、次はいよいよPDFを生成してファイルに出力してみます。
// TEST CODE: testing libharu
NSString *path = nil;
const char *pathCString = NULL;
NSLog(@"[libharu] PDF Creation START");
HPDF_Doc pdf = HPDF_New(NULL, NULL);
NSLog(@"[libharu] Adding page 1");
HPDF_Page page1 = HPDF_AddPage(pdf);
NSLog(@"[libharu] SetSize page 1");
HPDF_Page_SetSize(page1, HPDF_PAGE_SIZE_A4, HPDF_PAGE_LANDSCAPE);
NSLog(@"[libharu] TextOut page 1");
HPDF_Page_BeginText(page1);
HPDF_UseJPFonts (pdf);
HPDF_UseJPEncodings (pdf);
HPDF_Font fontEn = HPDF_GetFont(pdf, "Helvetica", "StandardEncoding");
HPDF_Font fontJp = HPDF_GetFont(pdf, "MS-Mincyo", "90ms-RKSJ-H");
HPDF_Page_SetFontAndSize(page1, fontEn, 16.0);
HPDF_Page_TextOut(page1, 100.00, 100.00, "Hello libHaru!");
HPDF_Page_SetFontAndSize(page1, fontJp, 16.0);
HPDF_Page_TextOut(page1, 100.00, 60.00, [@"はろー日本語" cStringUsingEncoding:NSShiftJISStringEncoding]);
HPDF_Page_EndText(page1);
NSLog(@"[libharu] Path drawing page 1");
HPDF_Page_SetLineWidth(page1, 4.0);
HPDF_Page_SetRGBStroke(page1, 1.0, 0, 0);
HPDF_Page_Rectangle(page1, 200, 200, 40, 150);
HPDF_Page_Stroke(page1);
NSLog(@"[libharu] PNG image drawing page 1");
path = [[NSBundle mainBundle] pathForResource:@"portrait2"
ofType:@"png"];
pathCString = [path cStringUsingEncoding:NSASCIIStringEncoding];
NSLog(@"[libharu] LoadPngImageFromFile path:%@\n pathCString:%s", path, pathCString);
HPDF_Image image = HPDF_LoadPngImageFromFile(pdf, pathCString);
HPDF_Page_DrawImage(page1, image, 300, 50, 245, 319);

// Get documents directory
NSArray *arrayPaths =
NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory,
NSUserDomainMask,
YES);
path = [arrayPaths objectAtIndex:0];
path = [path stringByAppendingPathComponent:@"test.pdf"];
pathCString = [path cStringUsingEncoding:1];
NSLog(@"[libharu] SaveToFile path:%@\n pathCString:%s", path, pathCString);
HPDF_SaveToFile(pdf, pathCString);
NSLog(@"[libharu] Freeing PDF object ");
HPDF_Free(pdf);
NSLog(@"[libharu] PDF Creation END");

これを実行すると、アプリのDocumentディレクトリにtest.pdfが生成されるはずです。


■実機で試してみる
シミュレータでは動くようになりましたが、実機で動かなければ何の意味もありません。ということで、いよいよ実機でテストしてみます!生成したPDFファイルを表示するためのUIWebViewを作成し、実機で表示してみた結果がこちら。



Hooray!!


■さて次は
いよいよ次からが本番、UIViewの中身をPDFに変換して出力する機能を作ってみようと思います!

2009年10月23日金曜日

iPhone向けに最適化されたPNGをlibpngで扱う方法

メモ。
iPhoneアプリのバンドルに同梱したPNGファイルは、ビルド時に最適化処理が行われてしまうため、そのままではlibpngで読み込むことが出来ません。iPhone向けに最適化されたPNGをlibpngで扱う方法は、いまのところ二つ。


1:最初から最適化をしないようにする
参考にしたページはこちら。
http://d.hatena.ne.jp/wasabi-arts/20090301/1235856525


このように、IPHONE_OPTIMIZE_OPTIONS=-skip-PNGsを追加するか、


またはこのように、最初っから圧縮をしないように設定する。


2:最適化したファイルをいったんUIImageで読み出して、再度PNGでファイル書きだしする
いったんUIImageを使って当該ファイルをロードして、ファイルにpng形式で書き出せばよいらしいです。
(@nakamura001さんありがとうございます!)

[2009/10/24 22:20追記]
@nakamura001さんがご自身のブログで検証結果をアップされてます。
http://d.hatena.ne.jp/nakamura001/20091024/1256371800

UIButtonからPNG画像を抜く方法ですが、これを応用すればUIButtonに最適化されたPNGを使用していても普通のPNG画像を取得することが出来ます。私の環境でもテストしたところ、うまくいきました!

2009年10月17日土曜日

PythonのEvernote APIをProxyに対応させることに成功しました

前回記事で見事大失敗したので、今回改めてThriftのTHttpClientのPython実装にProxy機能を持たせるチャレンジを行いました。結果、無事成功しました!さっそく共有させていただきたいと思います。


■PythonのEvernote APIをProxyに対応させる方法
まずはEvernote APIの中で使用されている通信クラス、thrift.transport.THttpClientを修正する必要があります。
以下のようにTHttpClient.pyを修正してください(または別名ファイルとして保存してください)。

http://gist.github.com/204501

修正しましたら、APIを呼びだす側のコードを以下のように変更してください。
# これを・・・
# import thrift.transport.THttpClient as THttpClient
# こうする
import thrift.transport.MyTHttpClient as THttpClient
import evernote.edam.userstore.UserStore as UserStore
import evernote.edam.userstore.constants as UserStoreConstants
import evernote.edam.notestore.NoteStore as NoteStore
import evernote.edam.type.ttypes as Types

##########
# 中略
##########

# プロキシを使わなくて良いときはproxy引数を無視すればよい
# userStoreHttpClient = THttpClient.THttpClient(userStoreUri)
# プロキシを使いたいときはこうする
userStoreHttpClient = THttpClient.THttpClient(userStoreUri, proxy="myproxy.example.com:8080")
userStoreProtocol = TBinaryProtocol.TBinaryProtocol(userStoreHttpClient)
userStore = UserStore.Client(userStoreProtocol)

versionOK = userStore.checkVersion(applicationClientNameString,
                                   UserStoreConstants.EDAM_VERSION_MAJOR,
                                   UserStoreConstants.EDAM_VERSION_MINOR)
これだけで通信時にプロキシを介してくれるようになりました!


■何をやっているか
そもそもThriftのTHttpClientはプロキシを使うこと自体が全く想定されていない?作りになってまして、他の言語の実装でもすべてプロキシは使えるようになっていません。たとえばC#での実装はこちら(http://issues.apache.org/jira/secure/attachment/12391517/THttpClient.cs)ですが、これもプロキシは使えません。connection.Proxy = null;ってなってます。

というわけで、勝手に自分でTHttpClientを再実装してプロキシを使えるようにしています。あんまりよい方法ではないと思いますので、本番サーバーなどでは真似しないでください><


■前回からの改良点
前回はhttplib.HTTPを使うのを辞めて、httplib.HTTPConnectionクラスを使うようにしたのですが、コレがそもそもの大失敗でした。といいますか、httplib.HTTPのソースを見たら内部でばっちりhttplib.HTTPConnectionが使われてるじゃないですかorz
self.__http = httplib.HTTP(self.host, self.port)
self.__http._conn #これでhttplib.HTTPConnectionが取得できる
そして前回最大の失敗がread()メソッドが呼ばれるたびに接続をcloseしてしまっていたところです。httplib.HTTPの実装をみてようやく気づいたのですが、ここでは接続をCloseしてはいけないんですね・・・
これらを踏まえて、今回はTHttpClientの中でhttplib.HTTPをそのまま使うようにして解決しました。

他にもhttplib.HTTPResponseは以下のようにしてファイルポインタが取得できるとか
response = conn.getresponse()
response.fp # これがファイルポインタ。httplib.HTTP.read()でも使われている
httplibはとにかくドキュメントに書いてない仕様が多すぎです><

2009年10月11日日曜日

UIViewControllerのtouchesBeganとかtouchesEndedが上手く機能しなかったと思ったら・・・

ひさっびさに普通のUIKitを使ったiPhoneアプリを作ったりし始めたらかなりの範囲を忘れてしまっていて大ハマりしてます。中でも一番困ったのがこれ。
@implementation AbesiViewController

- (void)viewDidLoad
{
[super viewDidLoad];
self.wantsFullScreenLayout = YES;
}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}

- (void)dealloc
{
[super dealloc];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// なぜか出力されない
NSLog(@"touchesBegan");
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// なぜか出力されない
NSLog(@"touchesMoved");
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// なぜか出力されない
NSLog(@"touchesEnded");
}

@end
とまぁ、なんの変哲もないUIViewControllerにタッチを扱うためのイベントハンドラを搭載してみただけなのですが、コレがまぁ動かない動かない!

こういうときに考えられる原因は、だいたいがInterface BuilderでInteraction Enabledのチェックを入れ忘れているとか、ViewControllerとViewをつなぎ忘れているとかそんなのばっかりなので真っ先に調べてみました。が、やはり問題は見つかりません。

試しにUIViewControllerではなくUIViewにtouchesBeganを載せてみるとコレが動くんです。ああ、OS 3.1.2ぐらいから挙動が変わったのかなとか思っていたら、

http://stackoverflow.com/questions/1025574/uiviewcontroller-not-receiving-touchesbegan-message
OK, I'm a dummy. It works fine. The problem was, I didn't realize I was sending a release message to the UIViewController without having retained it elsewhere first. So that was causing the problem.
あーーー!そうだ!!!UIViewControllerをretainしてない!!!orz

retainしたら解決しました。ほんと腕がなまってる・・・

2009年10月8日木曜日

PythonのEvernote APIをプロキシに対応させようとして挫折した

Evernote APIで使用されているThriftのTHttpClientクラスには、プロキシの設定を行うためのインターフェースが用意されていません。そのため、プロキシ越しにEvernote APIの呼び出しを行うことが出来ないみたいです。
(ひょっとしたら出来るのかもしれませんが、調べた範囲ではわからず)

そこでThriftのTHttpClientクラスのPython実装をプロキシに対応させるべくチャレンジしてみました。

http://gist.github.com/204501

基本的には元々httplib.HTTPで実装されていたところをhttplib.HTTPConnectionに変えただけなのですが、ところがこれがうまくいかない。このクライアントで/edam/user/にCheckVersionリクエストを投げると500エラーが返却されてしまいます。で、500エラーを拾うと再度試行するようになっているのか、あえなく無限ループに突入><
プロキシの設定が間違っているのかと思い、試しにプロキシを外してデバッグログをはかせた結果がこちら。
send: 'POST /edam/user HTTP/1.1\r\nHost: sandbox.evernote.com:443\r\nAccept-Encoding: identity\r\nHost: sandbox.evernote.com\r\nContent-Type: application/x-thrift\r\nContent-Length: 82\r\n\r\n'
send: '\x80\x01\x00\x01\x00\x00\x00\x0ccheckVersion\x00\x00\x00\x00\x0b\x00\x01\x00\x00\x00(Python URL to Evernote/0.1; Python/2.5.2\x06\x00\x02\x00\x01\x06\x00\x03\x00\r\x00'
debug: HTTPConnection
Request-sent None
reply: 'HTTP/1.1 200 OK\r\n'
header: Server: Apache-Coyote/1.1
header: Content-Type: application/x-thrift
header: Content-Length: 29
header: Date: Wed, 07 Oct 2009 09:04:29 GMT
read start
read POST request
send: 'POST /edam/user HTTP/1.1\r\nHost: sandbox.evernote.com:443\r\nAccept-Encoding: identity\r\n\r\n'
read getresponse
debug: HTTPConnection
Request-sent None
reply: 'HTTP/1.1 500 \r\n'
header: Server: Apache-Coyote/1.1
header: ETag: W/"3401-1254794526000"
header: Last-Modified: Tue, 06 Oct 2009 02:02:06 GMT
header: Content-Type: text/html
header: Content-Length: 3401
header: Date: Wed, 07 Oct 2009 09:04:29 GMT
header: Connection: close
read start
read POST request
うーん、HTTPSの実装が間違っているのか、リクエストパラメータが間違っているのか。1回目のリクエストはうまくいくけど2回目のリクエストが失敗しがちみたいです。もう少し調査してみます。


あ!今気づいた!!
2回目のリクエスト時(readメソッド実行時)のHTTPヘッダがめちゃくちゃだ!!ここを直せば動くかも。

2009年10月5日月曜日

Evernote APIためしてみた


API Keyくださいとお願いしたらすぐに発行してもらえたので、試しにEvernote APIを触ってみました。


■準備
http://blog.lilyx.net/2008/11/03/evernote-api/を見ると発行するときに手間がかかったとの記述がありましたが、私の場合は半日で発行されました。まぁ1年たってますし、状況が良くなったのかもしれませんね。申請時にアプリの詳細を事細かに書いたのが良かったのかもしれません。


■Hello Worldを動かす
まず最初にsandbox環境に自分用のアカウントを作成します。http://sandbox.evernote.com/login.jspにアクセスして、いつも通りアカウントを作成。忘れがちなので注意です。
アカウントが出来たら、ダウンロードしてきたSDKに最初っからサンプルスクリプトがついてきているので、こちらを実行するだけです。今回はPythonのサンプルスクリプトを試してみました。
cd sample/python
cp * ../../lib/python
cd ../../lib/python
python EDAMTest.py username password


■ENMLを試してみる
今回の最終目的は「はてブみたいにURLだけ渡したら勝手にEvernoteに取り込んでくれる」ことなので、まずはどのぐらい気軽にHTMLを取り込めるかを調べてみました。Evernoteの本文はENMLというHTMLに似たマークアップ言語で記述されています。
http://www.evernote.com/about/developer/api/evernote-api.htm#_Toc200272588
こちらの記載を読んでみたところ、かなーーーーーり面倒くさいということが判明。htmlタグやbodyタグが含められないのはまだいいとして、id属性もclass属性も許可されません。さらに困ったことに・・・

pタグ閉じてないからダメー。


brタグ閉じてないからダメー。

さすがXML、厳しい。どうやらHTMLとは全く互換性がないと考えた方が良さそうです。
HTMLをENMLに変換するライブラリとかないかなー?

ENMLは死ぬほど面倒ですが、それ以外のところは今のところ簡単です。

2009年10月4日日曜日

cocos2dでParticleSystemを使うときのメモ

懲りずにcocos2dで遊んでます。今回はParticleSystemを触ったときに気づいたことなどをメモしてみます。


■ParticleSystemが放出するParticleを回転させたい
http://www.cocos2d-iphone.org/api-ref/latest-stable/interface_particle_system.html
こちらの公式リファレンスには2009/10/02現在記載されていませんが、startSpin, startSpinVar, endSpin, endSpinVarというプロパティが用意されており、こちらの値を調整することで放出される個々のParticleに回転を与えることが出来ます。
        // angle of particles
startSpin = 90;
startSpinVar = 0;
endSpin = 1800;
endSpinVar = 3600;
注意点として、PointParticleSystemを継承したParticleSystemでは、spin系のプロパティは利用できないみたいです。パフォーマンスで劣るそうですが、QuadParticleSystemを継承するようにしましょう。

結果はこんな感じ。青いミミズみたいなのがParticleSystemから放出しているParticleです。
回転の中心軸の位置がおかしいのかな?


■Particleの大きさを変化させたくない
endSizeにkParticleStartSizeEqualToEndSizeを指定すると、放出されたParticleの大きさが変化しなくなります。
        // size, in pixels
startSize = 90.0f;
startSizeVar = 30.0f;
endSize = kParticleStartSizeEqualToEndSize;
endSizeVar = 30.0f;


■応用:Particleの回転を変化させたくない
現在のcocos2dではまだサポートされていませんので、適当にParticleSystem.mのコードを書き換えます。具体的には180行目のあたりを以下のようにします。
 // angle
float startA = startSpin + startSpinVar * CCRANDOM_MINUS1_1();
particle->angle = startA;
if (endSpin == kParticleStartSpinEqualToEndSpin )
particle->deltaAngle = 0;
else {
float endA = endSpin + endSpinVar * CCRANDOM_MINUS1_1();
particle->deltaAngle = (endA - startA) / particle->life;
}
これで放出されたParticleがくるくる回転するのを防げます。でもこのマジックナンバーを使った実装、正直イマイチです。
 // angle
float startA = startSpin + startSpinVar * CCRANDOM_MINUS1_1();
particle->angle = startA;
if (spinFixed)
particle->deltaAngle = 0;
else {
float endA = endSpin + endSpinVar * CCRANDOM_MINUS1_1();
particle->deltaAngle = (endA - startA) / particle->life;
}
個人的にはこの方に実装するほうが好きです。パフォーマンスは悪いかも。

各種WebサービスのAPI認証方法を調べてみた

自分でWeb サービスを作る際に、APIの認証ってどうやって作ればよいのだろうと思い立ち、各種Web サービスのAPIの認証方法を調べてみました。


■Google
参考にしたページはこちら。
http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI
認証方法
ユーザーIDおよびパスワードを元に、Cookieを生成
認証時のAPI通信
HTTPS POST
認証URL
https://www.google.com/accounts/ClientLogin
認証後のAPI通信
HTTP GET/POST, HTTPS GET/POST
トークン送出方法
HTTPリクエストヘッダのCookie属性に「SID」を含める
Cookieを使う方法ですので、Webブラウザを用いるWebアプリケーションに対する認証の場合は非常に簡単ですが、クライアントアプリケーションの場合は、認証URLのレスポンスを元にSIDを保存し、HTTPリクエストヘッダのCookie属性にSIDを追加する処理を自分で行う必要があるため、ちょっとだけ面倒です。それでもシンプルで分かりやすい方法だと思います。


■Remember the Milk(RTM)
参考にしたページはこちら。
http://www.rememberthemilk.com/services/api/authentication.rtm
http://www.rememberthemilk.com/services/api/methods/rtm.auth.getFrob.rtm
認証方法
APIキー、開発者とサーバー間の共通鍵、使い捨てセッションの3つを元に、セッションIDを生成
認証時のAPI通信
HTTP GET
認証URL1(使い捨てセッションの取得)
http://api.rememberthemilk.com/services/rest/?method=rtm.auth.getFrob&api_key=abc123
認証URL2(本認証)
http://www.rememberthemilk.com/services/auth/?api_key=abc123&perms=delete&frob=123456&api_sig=zxy987
認証URL2(認証済みセッションIDの取得)
http://api.rememberthemilk.com/services/rest/?method=rtm.auth.getToken&api_key=abc123&frob=123456

認証後のAPI通信
HTTP GET
トークン送出方法
HTTPパラメータに「auth_token」を含める
Webアプリケーションだけではなくクライアントアプリケーションへの対応を行っているためか、先ほどのGoogleのAPIと比べて格段に難しくなります。その代わり、HTTPS POSTを用いなくても、HTTP GETのみで認証を行うことが出来ます。こういうのをRESTって言うんでしょうか?正直良く分からず。
以下、クライアントアプリケーションでの認証手順。

1.APIキー申請をすると、以下の2つの値がもらえる
api_key・・・開発者がサーバーからもらえる公開鍵。公開鍵なので外に漏れても良い。
shared secret・・・開発者とサーバーが持つ共通鍵。絶対に外に漏れてはならない。

2.api_keyを元に、使い捨てセッションを生成する
frob・・・使い捨てセッション。以降、認証手続きの間のみ利用する。
frobの意味についてはhttp://en.wikipedia.org/wiki/Frobを参照。

3.shared secretと認証URLのリクエストパラメータを元に、api_sigを生成する。
認証URLには以下の3つのリクエストパラメータがつきます。
api_key=abc123&perms=delete&frob=123456
これを記号を抜いて結合して、
api_keyabc123permsdeletefrob123456

先頭にshared secretをつけて、
BANANASapi_keyabc123permsdeletefrob123456

md5 hash値を計算します。計算結果がapi_sigになります。
md5('BANANASapi_keyabc123permsdeletefrob123456')

4.api_key, frob, api_sigの値を元に、ユーザー認証画面を開いてIDとパスワードを入力してもらう
ここで認証URL2をユーザーにブラウザで開いてもらって、IDとパスワードを入力してもらえば認証が完了します。

5.認証が完了したセッションIDを取得する
auth_token・・・セッションID。次以降のリクエストには、全てこのauth_tokenをHTTPリクエストパラメータとして含める。


■Evernote
参考にしたページはこちら。
http://www.evernote.com/about/developer/api/evernote-api.htm#_Toc200272584
認証方法
メールアドレス、パスワード、consumerKey, consumerSecretを元に、セッションIDを生成
認証時のAPI通信
Thrift TBinaryProtocol wrapping a THttpClient transport
認証URL
https://www.evernote.com/edam/user
認証後のAPI通信
Thrift
トークン送出方法
UserStore Authentication Token?を付与する
Thriftというフレームワークが使われているようです。Thriftの実装がC, Java, PHP, Python, Perl, Rubyなどで用意されていて、クライアントはこれらの実装を用いて認証すればよいらしいです。詳細は良く分かりませんが、こちらもAPI Keyと共通鍵を利用しているため、比較的RTMの認証に近いことをしているように見えます。
ちなみに上記はクライアントアプリケーション用の認証で、Webアプリケーション用の認証にはOAuthを使用しています。OAuthについてはまた別の機会に調べます。


■Twitter
参考にしたページはこちら。
http://usy.jp/twitter/index.php?Twitter%20API
http://twitter.pbworks.com/API%20Docs#Authorization
http://watcher.moe-nifty.com/memo/2007/04/twitter_api.html
認証方法1
ユーザーID、パスワードを利用したBasic認証
認証時のAPI通信
なし
認証URL
なし
認証後のAPI通信
HTTP GET
トークン送出方法
http://username:password@twitter.com/のようにしてユーザーIDとパスワードを送出する
認証方法2
ユーザーIDおよびパスワードを元に、Cookieを生成
認証時のAPI通信
HTTP POST(HTTPSは無い?)
認証URL
http://twitter.com/login
認証後のAPI通信
HTTP GET
トークン送出方法
HTTPリクエストヘッダのCookie属性に「_twitter_session」を含める
認証方法3
OAuth
詳細については不明
以下、http://watcher.moe-nifty.com/memo/2007/04/twitter_api.htmlから転載。

public_timeline の取得等一部の API を除くほとんどの API で、認証を使用する。応答に protected なユーザに関する情報が含まれる可能性のある API は認証が必須となっている。
現在、OAuth認証とBASIC認証が使用可能。

トークンベースの認証 OAuth は、今のところベータ版であるが、将来、OAuth 認証を標準的な認証方法にする予定。
BASIC認証で使用するユーザ名はメールアドレスまたはスクリーン名のどちらでも構わない。

Webブラウザ等を経由して Twitter にログインしたときに発行される cookie を使うことで BASIC 認証の代わりにすることもできるが、公式には cookie を使っての API 実行はサポートしない。
(訳者による注記: API によっては cookie 使用時も BASIC 認証が要求されるものもある。また、BASIC認証での API 実行と cookie を利用しての API 実行では、異なる結果が返る API もある。特に、protected なユーザに関する挙動に違いが見られる。
OAuth 認証時も API の応答に cookie が含まれるが、次回 API 実行時にこの cookie をサーバーへ送り返す必要はない)。

もうこれを見るだけでばっちりでした。