iCalとiPhoneをTODO込みで同期をとる方法
iCalとiPhoneを連携させるには Google カレンダーを媒介として同期を取る方法が定番ですが,残念なことに TODO(リマインダー)の同期が取れません。
GoTasksなど,iPhoneからGoogle の「ToDo リスト」を参照できるようにするAppは存在するようなのですが,iCalとGoogleを連携させる方法がどうしても見つかりませんでした。
そこで発想を変えて Yahoo! Calendar を媒介とする方法を実験してみたところ,うまくいきましたので報告します。
iPhoneにYahoo!アカウントの情報を登録する
つぎは,iPhone に Yahoo! アカウントの情報を登録します。
「設定」 → 「メール/連絡先/カレンダー」 と進んで「アカウントを追加…」を選択し,「Yahoo!」のロゴを選びます。
これで,iPhone と Yahoo! Calendar の同期が取れるようになりました。
「カレンダー」アプリを起動すると,スケジュールが確認できます。
ただし,iOSの標準アプリだけでは TODO を参照することができません。
ここまでうまく行ったのに残念!
BusyToDoをインストールして,Yahoo!アカウントの情報を登録する
そこで,Yahoo! Calendar のクライアントとなるアプリがないか色々探したのですが見つかりません。
最後に,iCalがCalDAVを使っているのを思い出して,CalDAVに対応しているタスク管理アプリを試してみたところ,BusyToDo(850円)で成功しました!!
BusyToDoは,本来MobileMeを想定してCalDAVに対応しているようですが,「ユーザ名」にyahooのメールアドレスを入力したところあっさり接続できました。
他には,2Do(600円)がMobileMe(CalDAV)に対応しているようだったので試してみましたが,こちらはうまく接続できませんでした。
まとめ
Yahoo! Calendar を媒介とすることで,iCalとiPhoneのスケジュールとタスクを同期させることに成功しました。主に使用するiCalでスケジュールとタスクの管理が一元化できるようになったので,とてもすっきりしました。
ただ,iCloud のサービスが開始されれば,このようなテクニックは不要になるんでしょうね。
jarファイルとjniライブラリのインストール方法
(c) YsPhoto|写真素材 PIXTA
Javaの外部ライブラリ(*.jar)をJavaの処理系が参照できるようにするためには,環境変数CLASSPATHに追加したり,コマンド実行時に -classpath オプションを追加するなどの方法がありますが,jarファイルの数が多くなってくるといちいち指定するのは面倒です。
Java 2 (JDK 1.2)からの機能拡張により,システム標準のjarファイル置き場が設けられているので,OS別に情報をまとめました。
同時に,jniライブラリ(*.so, *.dll, *.jnilib)の置き場所についても記しています。
なお以降の説明では,Javaの開発環境(JDK)のインストールディレクトリを環境変数JAVA_HOMEに,実行環境(JRE)のインストールディレクトリを環境変数JRE_HOMEに設定してあるものとします。
Linux
jarファイルの置き場所は,以下のとおりです。
jniライブラリ(*.so)の置き場所は,以下のとおりです。
<アーキテクチャ>の部分は,32ビット環境では“i386”に,64ビット環境では“amd64”になるようです(JDK 1.6で確認)。
Windows
jarファイルの置き場所は,以下のとおりです。
Windowsの場合,jniライブラリ(*.dll)の置き場所として特別な場所が用意されているわけではなく,PATHが通っていればどこでも良いようです。
2011-08-29 訂正
8/25の版では,Windows用jniライブラリ(*.dll)の置き場所を「%JAVA_HOME%\jre\lib\<アーキテクチャ>」または「 %JRE_HOME%\lib\<アーキテクチャ>」と書きましたが,JDK 1.6.0で確認したところ誤りでした。32ビット版JDKをインストールするとi386というフォルダができますが,ここにDLLをコピーしてもjava起動時に検索できずエラーとなります。
PATHの設定が空の状態で以下のプログラムを実行して,実際の検索パスを調べたところ,
public class LibraryTest { public static void main(String[] args) { System.out.println(System.getProperty("java.library.path")); } }
次のようになりました。
C:\Program Files\Java\jdk1.6.0_24\bin;.;C:\WINDOWS\Sun\Java\bin;
C:\WINDOWS\system32;C:\WINDOWS
「上記の検索パス」+「PATHの設定」が,java実行時のjniライブラリの検索パスとなると思われます。
実用上は「%JAVA_HOME%\bin」に置くのが良さそうです。
Windows用PHP拡張モジュールを最短でビルドする方法
現在,「LL言語用PDF帳票ツール Field Reports」をWindowsへ移植しているのですが,Windows環境でPHP拡張モジュールをビルドする方法として断片的な情報しか見つからなくて苦労しました。
試行錯誤してなんとかビルド手順が確立できたので,わかったことをまとめておきます。
基礎知識
コンパイラ
基本的に,拡張モジュールをビルドする時は,PHPの実行モジュールをビルドした時と同じ環境でビルドする必要があるようです。
PHP起動時にチェックをしているようで,異なったビルド環境で作られた拡張モジュールを使おうとするとエラーとなりロードできません。
以下の条件をそろえる必要があるようです。
ちなみに,PHP公式サイトで配布されているWindows用バイナリモジュールは,PHP5.2がVisual Studio 6.0で,PHP5.3がVisual Studio 2008でビルドされていました。
PHP拡張モジュールをバイナリ配布するには,Visual Studio 6.0とVisual Studio 2008でビルドする必要がありそうです。
Windows版にはphpizeコマンドがない
Linux等のUnix系OSでの作成手順は,【PHP/C】PHP extension で Hello World! - 不動産に興味を持ち始めたWebエンジニアのメモ帳などを参照していただきたいのですが,標準的なビルド手順で必ず出てくる“phpize”コマンドがWindows版では用意されていません。
代わりに,ソースツリーのトップにある“buildconf.bat”コマンドを使えばよいと思われますが,任意のディレクトリに拡張モジュールのソースを展開して開発することは考慮されていないようです。
拡張モジュールのプログラミング
この記事では,Windowsで空の拡張モジュールを作成してビルドするまでの手順を扱っています。
拡張モジュールの中身のプログラミングについては,実例で学ぶPHP拡張モジュールの作り方|gihyo.jp … 技術評論社などを参照してください。
準備
Visual Studio
Visual Studio 6.0は中古で入手するしかないので,まずはVisual Studio 2008でPHP5.3用のモジュールを作成しました。
Visual Studio 2008 Express Editionはまだ入手できるようです。下記のマイクロソフトのサイトからダウンロードしてインストールしました。
http://www.microsoft.com/japan/msdn/vstudio/2008/product/express/
PHPの実行モジュール
PHP公式サイトからWindows用バイナリモジュールをダウンロードして展開します。
http://windows.php.net/download/
ここでは,展開したファイルを“C:\php”に置いたものとします。
PHPのソース
PHP公式サイトからPHPのソース一式をダウンロードします。
http://www.php.net/downloads.php
ここでは,展開したソースを“C:\php-src”に置いたものとします。
ビルド手順
コマンドプロンプトを開く
「スタート」メニューから「Visual Studio Tools/Visual Studio 2008 コマンドプロンプト」を選択し,コマンドプロンプト・ウインドウを開きます。
以後の作業は,このコマンドプロンプト上で行います。
スケルトンの作成
拡張モジュールのソースが展開されているディレクトリに移動し,PHPで記述されたスケルトン作成コマンドを実行します。
> cd C:\php-src\ext > php ext_skel_win32.php --extname=test
ここでは,モジュール名を“test”としました。
config.w32ファイルの編集
作成されたディレクトリの中にある“config.w32”ファイルをテキストエディタで編集します。
// $Id$ // vim:ft=javascript // If your extension references something external, use ARG_WITH // ARG_WITH("test", "for test support", "no"); // Otherwise, use ARG_ENABLE // ARG_ENABLE("test", "enable test support", "no"); if (PHP_TEST != "no") { EXTENSION("test", "test"); }
コメント部分を編集してARG_WITHの行かARG_ENABLEのどちらかの行を有効にします。
この行を有効にすると,後で実行するconfigureコマンドで“--enable-test”や“--with-test”オプションが選択できるようになります。今回は拡張モジュールのビルドだけが目的なので,一見無駄な作業ですが,編集しないとconfigureコマンドが動かないのでやはり編集する必要があります。
なお,ARG_ENABLEの行の先頭のコメントだけ削除して済ませようとするとconfigureコマンドが解析に失敗するので,以下の例のように余分なコメントはバッサリ削除します。
// $Id$ // vim:ft=javascript ARG_ENABLE("test", "enable test support", "no"); if (PHP_TEST != "no") { EXTENSION("test", "test"); }
configureコマンドの実行
ソースツリーのトップへ移って,以下のコマンドを実行します。
> cd C:\php-src > buildconf > configure
これで,コンパイルに必要なヘッダファイル“main/config.w32.h”が作成されます。
コンパイル
“C:\php-src\ext\test\test.dsp”ファイルをVisual Studio 2008で開きます。
.dspファイルはVisual Studio 6.0形式のプロジェクトファイルなので,2008形式のプロジェクトに変換する必要があります。
ビルド構成メニューで「Release_TS」を選択し,プロジェクトをビルドします。
おそらく以下のようなエラーが発生すると思いますので,プロジェクトのプロパティダイアログを開いて「構成プロパティ/リンカ/全般」の「追加のライブラリ ディレクトリ」に「C:\php\dev」を追加します(もしくは,php5ts.libを参照できる場所にコピーします)。
LINK : fatal error LNK1181: 入力ファイル 'php5ts.lib' を開けません。
こんどは,ビルドが成功するはずです。
「C:\php-src\Release_TS\php_test.dll」が作成されました。
ノンスレッドセーフ モジュールのビルド
デフォルトで作成されるのは,スレッドセーフの拡張モジュールになります。
ノンスレッドセーフのモジュールをビルドする場合は,
- 「プリプロセッサの定義」プロパティから“ZTS=1”の定義を削除します。
- 「リンカ/入力/追加依存ファイル」で“php5ts.lib”を“php5.lib”に変更します。
LL言語向けPDF帳票ツールField ReportsがJavaに対応しました
弊社製品LL言語向けPDF帳票ツール Field ReportsをJavaに対応させました。
当初の構想としては,Java対応の手段としてOCaml-Javaを使ってみようかと思っていたのですが,今回お手軽にJNIを使ってJava用I/F (Java Bridge) を作成してみました。
作成したJava Bridgeは,Field Reports 1.2.1に同梱して,本日(2011.07.12)よりリリースしています。
ただ,Java対応の手段としては,OCaml-Java を使った方法ももう少し検討してみたいと考えています。
したがって,今回のJava Bridgeのリリースは「暫定版」の扱いといたします(将来的には,サポート対象外の扱いにする可能性があります)。
Javaプログラムの作成
nativeメソッド宣言を含むJavaプログラムを以下のように作成しました。
帳票の構成やデータなどの構造を持った情報をJSONで与えているので,非常にシンプルなAPIとなっています。
package jp.co.field_works; import sun.misc.HexDumpEncoder; /** * Reports.java * * Java bridge for Field Reports * * @author <a href="http://www.field-works.co.jp/">Field Works, LLC.</a> */ public class Reports { /** * 共有ライブラリのロードとモジュールの初期化 */ static { System.loadLibrary("Reports"); init(); } /** * Reportsモジュールの初期化を行う。 */ private native static void init(); /** * バージョン番号を取得します。 */ public native static String version() throws ReportsException; /** * ログ出力のレベルを設定します。 * 有効な値の範囲は0〜4です: * 0: ログを出力しない * 1: ERRORログを出力する * 2: WARNログを出力する * 3: INFOログを出力する * 4: DEBUGログを出力する * 1以上の値を設定した場合,標準エラー出力にログを出力します(初期値:0)。 */ public native static void setLogLevel(int n) throws ReportsException; /** * レンダリング・パラメータのデフォルト値を設定します。 * レンダリング・パラメータは,JSON形式の文字列で与えます。 */ public native static void setDefaults(String param) throws ReportsException; /** * レンダリング・パラメータparamを元にレンダリングを実行し, * 結果をバイト文字列として返します。 * レンダリング・パラメータは,JSON形式の文字列で与えます。 */ public native static byte[] renders(String param) throws ReportsException; /** * レンダリング・パラメータparamを元にレンダリングを実行します。 * 処理結果は,filenameで指定したファイルに出力されます。 * レンダリング・パラメータは,JSON形式の文字列で与えます。 */ public native static void render(String param, String filename) throws ReportsException; public static void main(String[] args) throws ReportsException { // test Reports.setLogLevel(3); System.out.println("version: " + Reports.version()); HexDumpEncoder hexdumpencoder = new HexDumpEncoder(); System.out.println(hexdumpencoder.encodeBuffer(Reports.renders("{}"))); } }
Cプログラムの作成
javahプログラムで生成したヘッダファイルでの宣言にしたがって,Cプログラムを記述しました。
今までのPython, Ruby, Perl, PHPの言語Bridgeでは,LL言語ネイティブの辞書/ハッシュ等のデータ構造をCプログラム内でJSON相当のデータ構造に変換していたのですが,以下のような理由からJava BridgeではJSON文字列形式のみの対応としました。
- JSONICなどのJava用JSONライブラリを見てみると,Map, Array/Listだけではく,Object, Iterable, Iteratorなど多彩なオブジェクトからの変換に対応しており,これと同レベルの変換プログラムをCで記述するのは非常に困難。
- Clojure, Scala, GroovyなどのJVM上で動作するJava以外の言語では,独自のMap, Listのデータ構造を採用していることが多く,それらすべてに対応することは事実上不可能と考えた。
- JSON変換とPDF生成の処理時間を比べると,PDF生成にかかる時間が圧倒的に大きく,JSON変換をCで記述してもパフォーマンス向上にあまり貢献しないことがわかった。
ビルドには,OCamlのインクルードファイルが必要です。
2011.11.28 追記
Field Reports 1.3.0からは,OCamlのインクルードファイルが不要になりました。
#include <jni.h> #include <stdio.h> #include <string.h> #include "jni_reports.h" extern "C" { #include <caml/mlvalues.h> #include <caml/alloc.h> #include <caml/callback.h> #include <caml/fail.h> #include <caml/memory.h> #include <caml/threads.h> #include "reports.h" } static void throw_exn(JNIEnv* env, value exn) { jclass clazz = env->FindClass("jp/co/field_works/Reports/ReportsException"); env->ThrowNew(clazz, String_val(string_of_exn(Extract_exception(exn)))); } static value encode_string(JNIEnv* env, jstring jstr) { CAMLparam0(); CAMLlocal1(result); const char* s = env->GetStringUTFChars(jstr, NULL); result = caml_copy_string(s); env->ReleaseStringUTFChars(jstr, s); CAMLreturn(result); } /* * Class: jp_co_field_works_Reports * Method: init * Signature: ()V */ JNIEXPORT void JNICALL Java_jp_co_field_1works_Reports_init(JNIEnv* env, jclass clazz) { init_reports(); } /* * Class: jp_co_field_works_Reports * Method: version * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_jp_co_field_1works_Reports_version(JNIEnv* env, jclass clazz) { jstring jresult = NULL; CAMLparam0(); CAMLlocal1(caml_result); caml_leave_blocking_section(); caml_result = reports_version(); if (Is_exception_result(caml_result)) { // Error throw_exn(env, caml_result); goto exit; } jresult = env->NewStringUTF(String_val(caml_result)); exit: caml_enter_blocking_section(); CAMLreturnT(jstring, jresult); } /* * Class: jp_co_field_works_Reports * Method: setLogLevel * Signature: (I)V */ JNIEXPORT void JNICALL Java_jp_co_field_1works_Reports_setLogLevel(JNIEnv* env, jclass clazz, jint n) { CAMLparam0(); CAMLlocal2(caml_level, caml_result); caml_leave_blocking_section(); caml_level = Val_int(n); caml_result = set_log_level(caml_level); if (Is_exception_result(caml_result)) { // Error throw_exn(env, caml_result); goto exit; } exit: caml_enter_blocking_section(); CAMLreturn0; } /* * Class: jp_co_field_works_Reports * Method: setDefaults * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_jp_co_field_1works_Reports_setDefaults(JNIEnv* env, jclass clazz, jstring param) { CAMLparam0(); CAMLlocal2(caml_param, caml_result); caml_leave_blocking_section(); caml_param = json_of_string(encode_string(env, param)); if (Is_exception_result(caml_param)) { // Error throw_exn(env, caml_param); goto exit; } caml_result = set_defaults(caml_param); if (Is_exception_result(caml_result)) { // Error throw_exn(env, caml_result); goto exit; } exit: caml_enter_blocking_section(); CAMLreturn0; } /* * Class: jp_co_field_works_Reports * Method: renders * Signature: (Ljava/lang/String;)[B */ JNIEXPORT jbyteArray JNICALL Java_jp_co_field_1works_Reports_renders(JNIEnv* env, jclass clazz, jstring param) { jbyteArray jresult = NULL; int len = 0; CAMLparam0(); CAMLlocal2(caml_param, caml_result); caml_leave_blocking_section(); // convert parameter caml_param = json_of_string(encode_string(env, param)); if (Is_exception_result(caml_param)) { // Error throw_exn(env, caml_param); goto exit; } // call OCaml function caml_result = pdf_of_json(caml_param); if (Is_exception_result(caml_result)) { // Error throw_exn(env, caml_result); goto exit; } // set return value len = string_length(caml_result); jresult = env->NewByteArray(len); env->SetByteArrayRegion(jresult, 0, len, (jbyte*)String_val(caml_result)); exit: caml_enter_blocking_section(); CAMLreturnT(jbyteArray, jresult); } /* * Class: jp_co_field_works_Reports * Method: render * Signature: (Ljava/lang/String;Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_jp_co_field_1works_Reports_render(JNIEnv* env, jclass clazz, jstring param, jstring filename) { CAMLparam0(); CAMLlocal3(caml_fname, caml_param, caml_result); caml_leave_blocking_section(); // convert parameter caml_param = json_of_string(encode_string(env, param)); if (Is_exception_result(caml_param)) { // Error throw_exn(env, caml_param); goto exit; } caml_fname = encode_string(env, filename); // call OCaml function caml_result = write_pdf_from_json(caml_param, caml_fname); if (Is_exception_result(caml_result)) { // Error throw_exn(env, caml_result); goto exit; } exit: caml_enter_blocking_section(); CAMLreturn0; }
Javaからの利用
完成したJava Bridgeを使って,JavaからField Reportsを利用するサンプルプログラムを以下に示します。
JSONライブラリにはJSONICを使用しました。
Map型のリテラルがないため煩雑なプログラムになってしまいましたが,実際のプログラムではデータベースなどから取得したデータを元に帳票を生成することになると思うので,問題ないでしょう。
import java.util.ArrayList; import java.util.HashMap; import net.arnx.jsonic.JSON; import jp.co.field_works.Reports; import jp.co.field_works.ReportsException; public class mitumori { public static void main(String[] args) throws ReportsException { // レンダリング・パラメータの作成 HashMap param = new HashMap(); param.put("template", "./mitumori.pdf"); HashMap context = new HashMap(); context.put("date", "平成23年7月11日"); context.put("number", "10R0001"); context.put("to", "△△△惣菜株式会社"); context.put("title", "肉じゃがの材料"); context.put("delivery_date", "平成23年7月31日"); context.put("delivery_place", "貴社指定場所"); context.put("payment_terms", "銀行振込"); context.put("expiration_date", "発行から3ヶ月以内"); HashMap icon = new HashMap(); icon.put("icon", "./stamp.png"); context.put("stamp1", icon); ArrayList table = new ArrayList(); table.add(new String[]{"1", "N001", "牛肉(切り落とし)", "200g", "250円", "500円"}); table.add(new String[]{"2", "Y001", "じゃがいも(乱切り)", "3個", "30円", "90円"}); table.add(new String[]{"3", "Y002", "にんじん(乱切り)", "1本", "40円", "40円"}); table.add(new String[]{"4", "Y003", "たまねぎ(くし切り)", "1個", "50円", "50円"}); table.add(new String[]{"5", "Y004", "しらたき", "1袋", "80円", "80円"}); table.add(new String[]{"6", "Y005", "いんげん", "1袋", "40円", "40円"}); context.put("table", table); context.put("sub_total", "800円"); context.put("tax", "40円"); context.put("total", "840円"); param.put("context", context); final String json = JSON.encode(param); System.out.println(json); // レンダリング実行 Reports.setLogLevel(3); Reports.render(json, "out.pdf"); } }
Clojureからの利用
試しにClojureでもサンプルプログラムを作ってみました。
非常にすっきりと書けますね。
(use 'clojure.contrib.json) (import '(jp.co.field_works Reports)) (def param { :template "./mitumori.pdf", :context { :date "平成23年7月11日", :number "10R0001", :to "△△△惣菜株式会社", :title "肉じゃがの材料", :delivery_date "平成23年7月31日", :delivery_place "貴社指定場所", :payment_terms "銀行振込", :expiration_date "発行から3ヶ月以内", :stamp1 {:icon "./stamp.png"}, :table [ ["1" "N001" "牛肉(切り落とし)" "200g" "250円" "500円"] ["2" "Y001" "じゃがいも(乱切り)" "3個" "30円" "90円"] ["3" "Y002" "にんじん(乱切り)" "1本" "40円" "40円"] ["4" "Y003" "たまねぎ(くし切り)" "1個" "50円" "50円"] ["5" "Y004" "しらたき" "1袋" "80円" "80円"] ["6" "Y005" "いんげん" "1袋" "40円" "40円"] ] :sub_total "800円", :tax "40円", :total "840円"}}) (Reports/setLogLevel 3) (Reports/render (json-str param) "./out.pdf")
まとめ
Field ReportsのJava用I/F (Java Bridge) を作成しました。
JNIを使えば,LL言語用の拡張モジュールを作成するのと同程度の手間で,Cモジュールを呼び出すI/Fが作成できることがわかりました。
Groovy, ScalaなどJVM上で動作しJSON出力が可能なプログラミング言語であれば,Field Reportsをご利用頂けると思われます。
Java用に限らず,Python, Ruby, Perl, PHP用の言語Bridge部分のソースは,BSDライセンスのオープンソースとしています。
よろしければ,さまざまなプログラミング言語からOCamlで作成したプログラムを呼び出すサンプルとしてご参照ください。
また,もし弊社で対応しきれないプログラミング言語用の拡張モジュールを作って頂ける方がいらっしゃれば歓迎します。