2010年11月21日日曜日

Evernote API を使ってみる (6) タグの作成・更新、タグつきノート

タグは単体で作成することも、ノートを作成or更新しながらついでに作ってしまうこともできる。
タグとノートはGUIDで紐づいているので、タグの名前を更新するとノートについているタグ名も変わる(当たり前だけど)。

タグを作成、更新するテストプログラム。expungeTag()はAPIキー発行時には許可されない(使えるようにするためには別途申請が必要)。

public class TagTest {

    private static String consumerKey = "consumer key";
    private static String consumerSecret = "consumer secret";
    private static String evernoteHost = "sandbox.evernote.com";
    private static String userName = "user name";
    private static String password = "password";

    private static NoteStore.Client setupNoteStore(String evernoteHost, User user) throws TException {
        // NoteStore クライアントのセットアップ
        String noteStoreUrl = "https://" + evernoteHost + "/edam/note/" + user.getShardId();
        THttpClient trans = new THttpClient(noteStoreUrl);
        TBinaryProtocol prot = new TBinaryProtocol(trans);
        NoteStore.Client noteStore = new NoteStore.Client(prot, prot);
        return noteStore;
    }

    public static void main(String[] args) {
        UserStore.Client userStore = null;
        NoteStore.Client noteStore = null;
        AuthenticationResult authResult = null;
        try {
            userStore = AuthUtil.setupUserStore(evernoteHost);
            authResult = AuthUtil.authenticate(userStore, consumerKey, consumerSecret, evernoteHost, userName, password);
            User user = authResult.getUser();
            String authToken = authResult.getAuthenticationToken();
            
            noteStore = setupNoteStore(evernoteHost, user);
            
            Notebook notebook = noteStore.getDefaultNotebook(authToken);
            
            // 新規タグの作成
            System.out.println("** CREATE TAG **");
            Tag tag1 = new Tag();
            tag1.setName("tag1");
            Tag created1 = noteStore.createTag(authToken, tag1);
            System.out.println("タグ\"" + created1.getName() + "\"が作成されました。(guid=" + created1.getGuid() + ")");
            Tag tag2 = new Tag();
            tag2.setName("tag2");
            Tag created2 = noteStore.createTag(authToken, tag2);
            System.out.println("タグ\"" + created2.getName() + "\"が作成されました。(guid=" + created2.getGuid() + ")");
            
            // タグつきのノートを作成
            Note note = new Note();
            note.setNotebookGuid(notebook.getGuid());
            note.setTitle("Testnote with Tags - " + new Date().getTime());
            note.addToTagGuids(created1.getGuid()); // 既存タグのGUIDを指定
            note.addToTagGuids(created2.getGuid()); 
            note.addToTagNames("tag3"); // ここで存在しないタグ名を指定すると自動的にタグが作成される
            Note note_created = noteStore.createNote(authToken, note);
            System.out.println("ノート\"" + note_created.getTitle() + "\"が作成されました。");
            
            // ノートについているタグを取得
            List<String> tagNames = noteStore.getNoteTagNames(authToken, note_created.getGuid());
            System.out.println("Tags: ");
            for (String tagName : tagNames) {
                System.out.println(" - " + tagName);
            }
            
            // タグの取得
            System.out.println("** GET TAG **");
            Tag tag1_1 = noteStore.getTag(authToken, created1.getGuid());
            System.out.println("タグ\"" + tag1_1.getName() + "\"を取得しました。");
            
            // タグの更新
            System.out.println("** UPDATE TAG **");
            tag1_1.setName("tag1_update");
            noteStore.updateTag(authToken, tag1_1);
            System.out.println("タグ\"" + tag1_1.getName() + "\"を更新しました。");
            

            // 全タグのリストを取得
            System.out.println("** LIST TAGS **");
            List<Tag> tags = noteStore.listTags(authToken);
            for (Tag tag : tags) {
                System.out.println(" - " + tag.getName());
            }
            
            // タグの削除
            // XXX デフォルトでは許可されない
            // System.out.println("** EXPUNGE TAG **");
            // noteStore.expungeTag(authToken, created1.getGuid());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

ちょっとはまったのが、ノートにつけられているタグ名を取得する方法。NoteStore#getNote()でとってきたノートに対してNote#getTagGuids()でGUIDのリストがとれるけれど、Note#getTagNames()だと、NullPointerException が発生してタグ名のリストがとれない。フォーラムで調べてみるとどうも、GUIDは入れてくれるけれどタグ名は入れてくれないらしい(整合性のためらしい)。getTagNames()はNote#addToTagNames()を使って(ローカルで)追加したタグ名だけをとるためのもののようだ(でもNullPointerExceptionはちょっとひどい・・・)。
GUIDのリストだけあっても仕方ないのでどうするのかと思ったら、一発でノートについているタグ名を取得するNoteStore#getNoteTagNames()というメソッドがあった。
参考: Evernote User Forum - tagNames is Empty

実行例。
** CREATE TAG **
タグ"tag1"が作成されました。(guid=dc4717bd-8faf-44d5-963e-c3a779e5faaa)
タグ"tag2"が作成されました。(guid=701dd298-ccda-4e70-b8d5-6c38b3d1291d)
ノート"Testnote with Tags - 1290311169789"が作成されました。
Tags: 
 - tag1
 - tag2
 - tag3
** GET TAG **
タグ"tag1"を取得しました。
** UPDATE TAG **
タグ"tag1_update"を更新しました。
** LIST TAGS **
 - tag1_update
 - tag2
 - tag3

Evernote API を使ってみる (5) リソースつきノート作成

画像やPDFといったノートの添付ファイルは「リソース」として扱う。リソースはノートに紐づくもので、単体で作成したり削除したりすることはできない(取得とメタデータの更新はできる)。リソースの扱いは(バイナリデータという性質上)他のタイプに比べるとかなり面倒。

リソースつきのノートを作成するには、以下の手順になる。

  1. 新規ノートを作成
  2. 新規リソースを作成
    -バイナリデータを設定
    -MIMEタイプを設定
    -必要ならファイル名など属性(メタデータ)を設定
  3. ノートにリソースを追加
  4. ノートのコンテンツにリソースを参照するためのen-mediaタグを追加
    -参照にはデータのハッシュ(MD5チェックサム)を使う。
  5. サービスのcreateNote()メソッドをコール
既存のノートにリソースを追加したり、削除したりするには、ノートが持っているリソースリストを変更したうえでupdateNote()をコールする。

リソースを取得するには、ノートからたどることも、リソース単体で取得することもできる。単体で取得するには、直接GUIDを指定する(そもそもリソースのGUIDなんかとってくるのが面倒><)か、ノートのGUID&リソースデータのハッシュ値を指定して取得する。ノートやリソースを取得する getNote() や getResource() には、バイナリデータやリソースの属性などを取得するかどうかのフラグがあって、必要なときだけ true を指定させるようになっている。必要ないものは全部 false にしておく。

リソースとリソースつきノートを操作するテストプログラムがこちら。
public class ResourceTest {

    private static String consumerKey = "consumer key";
    private static String consumerSecret = "consumer secret";
    private static String evernoteHost = "sandbox.evernote.com";
    private static String userName = "user name";
    private static String password = "password";

    private static NoteStore.Client setupNoteStore(String evernoteHost, User user) throws TException {
        // NoteStore クライアントのセットアップ
        String noteStoreUrl = "https://" + evernoteHost + "/edam/note/" + user.getShardId();
        THttpClient trans = new THttpClient(noteStoreUrl);
        TBinaryProtocol prot = new TBinaryProtocol(trans);
        NoteStore.Client noteStore = new NoteStore.Client(prot, prot);
        return noteStore;
    }

    // ファイルからバイナリストリームを読み込んでデータオブジェクトを作成するメソッド
    // 付属のサンプルコードからコピペしたもの
    private static Data readFileAsData(String fileName) throws Exception {
        FileInputStream in = new FileInputStream(fileName);
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        byte[] block = new byte[10240];
        int len;
        while ((len = in.read(block)) >= 0) {
            byteOut.write(block, 0, len);
        }
        in.close();
        byte[] body = byteOut.toByteArray();
        Data data = new Data();
        data.setSize(body.length);
        data.setBodyHash(MessageDigest.getInstance("MD5").digest(body));
        data.setBody(body);
        return data;
    }

    // ハッシュ値(バイナリ配列)を文字列に変換するメソッド
    // 付属のサンプルコードからコピペしたもの
    public static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte hashByte : bytes) {
            int intVal = 0xff & hashByte;
            if (intVal < 0x10) {
                sb.append('0');
            }
            sb.append(Integer.toHexString(intVal));
        }
        return sb.toString();
    }
    
    public static void main(String[] args) {
        UserStore.Client userStore = null;
        NoteStore.Client noteStore = null;
        AuthenticationResult authResult = null;
        try {
            userStore = AuthUtil.setupUserStore(evernoteHost);
            authResult = AuthUtil.authenticate(userStore, consumerKey, consumerSecret, evernoteHost, userName, password);
            User user = authResult.getUser();
            String authToken = authResult.getAuthenticationToken();
            
            noteStore = setupNoteStore(evernoteHost, user);
            
            Notebook notebook = noteStore.getDefaultNotebook(authToken);
            
            // リソースつき新規ノート作成
            System.out.println("** CREATE NOTE WITH RESOURCES **");
            Note note = new Note();
            note.setNotebookGuid(notebook.getGuid());
            note.setTitle("Testnote with Resources - " + new Date().getTime());
            
            // リソース作成
            Resource resource1 = new Resource();
            resource1.setData(readFileAsData("image/neko.jpg")); // バイナリデータ
            resource1.setMime("image/jpeg"); // MIMEタイプ
            ResourceAttributes atts1 = new ResourceAttributes(); // リソースの属性
            atts1.setFileName("neko.jpg");
            resource1.setAttributes(atts1);
            note.addToResources(resource1); // ノートにリソースを追加
            String hashHex1 = bytesToHex(resource1.getData().getBodyHash()); // リソースのハッシュ値(MD5チェックサム)
            
            Resource resource2 = new Resource();
            resource2.setData(readFileAsData("image/tori.jpg"));
            resource2.setMime("image/jpeg");
            ResourceAttributes atts2 = new ResourceAttributes();
            atts2.setFileName("tori.jpg");
            resource2.setAttributes(atts2);
            note.addToResources(resource2);
            String hashHex2 = bytesToHex(resource2.getData().getBodyHash());
            // ノートのコンテンツ。en-mediaタグにリソースのハッシュ値、MIMEタイプを入れる。
            String content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                + "<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml.dtd\">"
                + "<en-note>Pictures:<br/>"
                + "<en-media type=\"image/jpeg\" hash=\"" + hashHex1 + "\"/><br/>"
                + "<en-media type=\"image/jpeg\" hash=\"" + hashHex2 + "\"/>"
                + "</en-note>";
            note.setContent(content);
            
            Note created = noteStore.createNote(authToken, note);
            System.out.println("ノート\"" + created.getTitle() + "\"を作成しました。");
            System.out.println("ノート\"" + created.getTitle() + "\"のリソース数: " + created.getResourcesSize());
            for (Resource resource : created.getResources()) {
                System.out.print(" - " + resource.getAttributes().getFileName());
                System.out.println(" (guid=" + resource.getGuid() + ")");
            }
            
            // リソースを追加してノートを更新
            System.out.println("** ADD RESOURCE **");
            Resource resource3 = new Resource();
            resource3.setData(readFileAsData("image/inu.jpg"));
            resource3.setMime("image/jpeg");
            ResourceAttributes atts3 = new ResourceAttributes();
            atts3.setFileName("inu.jpg");
            resource3.setAttributes(atts3);
            created.addToResources(resource3);
            String hashHex3 = bytesToHex(resource3.getData().getBodyHash());
            String content_update1 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                + "<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml.dtd\">"
                + "<en-note>Pictures:<br/>"
                + "<en-media type=\"image/jpeg\" hash=\"" + hashHex1 + "\"/><br/>"
                + "<en-media type=\"image/jpeg\" hash=\"" + hashHex2 + "\"/><br/>"
                + "<en-media type=\"image/jpeg\" hash=\"" + hashHex3 + "\"/>"
                + "</en-note>";
            created.setContent(content_update1);
            Note updated1 = noteStore.updateNote(authToken, created);
            System.out.println("ノート\"" + updated1.getTitle() + "\"を更新しました。");
            System.out.println("ノート\"" + updated1.getTitle() + "\"のリソース数: " + updated1.getResourcesSize());
            for (Resource resource : updated1.getResources()) {
                System.out.print(" - " + resource.getAttributes().getFileName());
                System.out.println(" (guid=" + resource.getGuid() + ")");
            }

            // リソースを削除してノートを更新
            System.out.println("** REMOVE RESOURCE **");
            Iterator<Resource> it = updated1.getResourcesIterator();
            while(it.hasNext()) {
                Resource r = it.next();
                if (bytesToHex(r.getData().getBodyHash()).equals(hashHex1)) {
                    it.remove();
                }
            }
            String content_update2 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                + "<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml.dtd\">"
                + "<en-note>Pictures:<br/>"
                + "<en-media type=\"image/jpeg\" hash=\"" + hashHex2 + "\"/><br/>"
                + "<en-media type=\"image/jpeg\" hash=\"" + hashHex3 + "\"/>"
                + "</en-note>";
            updated1.setContent(content_update2);
            Note updated2 = noteStore.updateNote(authToken, updated1);
            System.out.println("ノート\"" + updated2.getTitle() + "\"を更新しました。");
            System.out.println("ノート\"" + updated2.getTitle() + "\"のリソース数: " + updated2.getResourcesSize());
            for (Resource resource : updated2.getResources()) {
                System.out.print(" - " + resource.getAttributes().getFileName());
                System.out.println(" (guid=" + resource.getGuid() + ")");
            }
            
            // リソースをGUIDで指定して取得
            System.out.println("** GET RESOURCE BY ID **");
            String guid = updated2.getResources().get(0).getGuid();
            Resource r = noteStore.getResource(authToken, guid, false, false, true, false);
            System.out.println("リソース\"" + r.getAttributes().getFileName() + "\"を取得しました。(guid=" + r.getGuid() + ")");

            // リソースをノートGUIDとハッシュ値で指定して取得
            System.out.println("** GET RESOURCE BY NOTE ID AND HASH **");
            byte[] hash = updated2.getResources().get(0).getData().getBodyHash();
            Resource r2 = noteStore.getResourceByHash(authToken, updated2.getGuid(), hash, false, false, false);
            System.out.println("リソース\"" + r2.getAttributes().getFileName() + "\"を取得しました。(guid=" + r2.getGuid() + ")");

            // リソースを更新(メタデータのみ可)
            System.out.println("** UPDATE RESORCE **");
            String fileName = r2.getAttributes().getFileName();
            r2.getAttributes().setFileName("update_" + fileName);
            noteStore.updateResource(authToken, r2);
            Resource r2_updated = noteStore.getResource(authToken, r2.getGuid(), false, false, true, false);
            System.out.println("リソース\"" + r2_updated.getAttributes().getFileName() + "\"が更新されました。(guid=" + r2_updated.getGuid() + ")");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

実行例。
** CREATE NOTE WITH RESOURCES **
ノート"Testnote with Resources - 1290298760809"を作成しました。
ノート"Testnote with Resources - 1290298760809"のリソース数: 2
 - tori.jpg (guid=f2f0d0d6-3a5e-4007-867d-2bafccf4bcec)
 - neko.jpg (guid=c800bf58-0ee4-47b0-bc4b-a8556ea2b830)
** ADD RESOURCE **
ノート"Testnote with Resources - 1290298760809"を更新しました。
ノート"Testnote with Resources - 1290298760809"のリソース数: 3
 - tori.jpg (guid=f2f0d0d6-3a5e-4007-867d-2bafccf4bcec)
 - neko.jpg (guid=c800bf58-0ee4-47b0-bc4b-a8556ea2b830)
 - inu.jpg (guid=96815797-6ee6-4694-a784-6c81c9eaeb10)
** REMOVE RESOURCE **
ノート"Testnote with Resources - 1290298760809"を更新しました。
ノート"Testnote with Resources - 1290298760809"のリソース数: 2
 - tori.jpg (guid=f2f0d0d6-3a5e-4007-867d-2bafccf4bcec)
 - inu.jpg (guid=96815797-6ee6-4694-a784-6c81c9eaeb10)
** GET RESOURCE BY ID **
リソース"tori.jpg"を取得しました。(guid=f2f0d0d6-3a5e-4007-867d-2bafccf4bcec)
** GET RESOURCE BY NOTE ID AND HASH **
リソース"tori.jpg"を取得しました。(guid=f2f0d0d6-3a5e-4007-867d-2bafccf4bcec)
** UPDATE RESORCE **
リソース"update_tori.jpg"が更新されました。(guid=f2f0d0d6-3a5e-4007-867d-2bafccf4bcec)

2010年11月20日土曜日

Evernote API を使ってみる (4) ノート作成・取得・更新・削除

ノートを作成・取得・更新・削除するには、基本的にはノートブックの場合と一緒でNoteStoreサービスのメソッド(プロシージャ)をコールすればよい。削除(delete)は本当にデータを削除するわけではなく、Inactiveフラグを立てる(Evernoteサイトやデスクトップアプリでいう、Trashへ移動された状態)だけなので注意。データ自体を削除するには、expungeNotes()などを使う。

ノート作成・取得・更新・削除を行うテストプログラム。
public class NoteTest {

    private static String consumerKey = "consumer key";
    private static String consumerSecret = "consumer secret";
    private static String evernoteHost = "sandbox.evernote.com";
    private static String userName = "user name";
    private static String password = "password";

    private static NoteStore.Client setupNoteStore(String evernoteHost, User user) throws TException {
        // NoteStore クライアントのセットアップ
        String noteStoreUrl = "https://" + evernoteHost + "/edam/note/" + user.getShardId();
        THttpClient trans = new THttpClient(noteStoreUrl);
        TBinaryProtocol prot = new TBinaryProtocol(trans);
        NoteStore.Client noteStore = new NoteStore.Client(prot, prot);
        return noteStore;
    }

    public static void main(String[] args) {
        UserStore.Client userStore = null;
        NoteStore.Client noteStore = null;
        AuthenticationResult authResult = null;
        try {
            userStore = AuthUtil.setupUserStore(evernoteHost);
            authResult = AuthUtil.authenticate(userStore, consumerKey, consumerSecret, evernoteHost, userName, password);
            User user = authResult.getUser();
            String authToken = authResult.getAuthenticationToken();
            
            noteStore = setupNoteStore(evernoteHost, user);
            
            Notebook notebook = noteStore.getDefaultNotebook(authToken);
            
            // 新規ノート作成
            System.out.println("** CREATE **");
            Note note1 = new Note();
            note1.setTitle("testnote1");
            String content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                + "<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml.dtd\">"
                + "<en-note>Hello Evernote.</en-note>";
            note1.setContent(content);
            note1.setNotebookGuid(notebook.getGuid());
            Note created1 = noteStore.createNote(authToken, note1);
            System.out.println("ノート \"" + created1.getTitle() + "\" が作成されました。(guid=" + created1.getGuid() + ")");
            Note note2 = new Note();
            note2.setTitle("testnote2");
            note2.setContent(content);
            note2.setNotebookGuid(notebook.getGuid());
            Note created2 = noteStore.createNote(authToken, note2);
            System.out.println("ノート \"" + created2.getTitle() + "\" が作成されました。(guid=" + created1.getGuid() + ")");
            
            // ノート取得
            System.out.println("** GET **");
            Note note1_1 = noteStore.getNote(authToken, created1.getGuid(), true, false, false, false);
            System.out.println("ノート \"" + note1_1.getTitle() + "\" を取得しました。(Update Sequence Number: " + note1_1.getUpdateSequenceNum() + ")");
            System.out.println("コンテンツ: " + note1_1.getContent());
            
            // ノート更新
            System.out.println("** UPDATE **");
            String content2 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                + "<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml.dtd\">"
                + "<en-note>My First Note.</en-note>";
            note1_1.setContent(content2);
            noteStore.updateNote(authToken, note1_1);
            Note updated = noteStore.getNote(authToken, note1_1.getGuid(), true, false, false, false);
            System.out.println("ノート \"" + updated.getTitle() + "\" を更新しました。(Update Sequence Number: " + updated.getUpdateSequenceNum() + ")");
            System.out.println("コンテンツ: " + updated.getContent());
            
            // ノート削除 (Trashに移動)
            System.out.println("** DELETE **");
            int usn = noteStore.deleteNote(authToken, created2.getGuid());
            System.out.println("ノート\"" + created2.getTitle() + "\"が削除されました。");
            
            // ノートリストを取得
            System.out.println("** FIND **");
            NoteFilter filter = new NoteFilter();
            filter.setNotebookGuid(notebook.getGuid());
            NoteList noteList = noteStore.findNotes(authToken, filter, 0, 100);
            List<Note> notes = noteList.getNotes();
            System.out.println("(Active Notes)");
            for (Note note : notes) {
                System.out.println(" - " + note.getTitle());
            }
            
            // Trashに移動されたノートリストを取得
            filter.setInactive(true);
            NoteList noteList2 = noteStore.findNotes(authToken, filter, 0, 100);
            List<Note> notes2 = noteList2.getNotes();
            System.out.println("(Inactive Notes)");
            for (Note note : notes2) {
                System.out.println(" - " + note.getTitle());
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

実行例。
** CREATE **
ノート "testnote1" が作成されました。(guid=19ebdb55-c36b-47b5-8ce6-7ef9474b79aa)
ノート "testnote2" が作成されました。(guid=19ebdb55-c36b-47b5-8ce6-7ef9474b79aa)
** GET **
ノート "testnote1" を取得しました。(Update Sequence Number: 201)
コンテンツ: Hello Evernote.
** UPDATE **
ノート "testnote1" を更新しました。(Update Sequence Number: 203)
コンテンツ: My First Note.
** DELETE **
ノート"testnote2"が削除されました。
** FIND **
(Active Notes)
 - testnote1
(Inactive Notes)
 - testnote2

ノートのコンテンツは、Evernote Markup Language (ENML) で厳密に記述する必要がある(DTDがここ)。保存前にバリデーションがかかり、妥当なXMLでなければはじかれてしまうため、DOMなりのツールを使ってきちんと構築しないといけない。

ノートブックや、他のタイプでもそうなのだけれど、特定のオブジェクトを指定する場合にGUIDを指定しないといけないのがけっこう面倒くさい&通信が多くなる(ノートの場合はGUID以外に一意に特定できる属性がないから仕方ないけれど)。実際、アプリを作ろうという場合はGUIDをメモリなりにキャッシュしておくレイヤーをかぶせるのだと思う。

Evernote API を使ってみる (3) ノートブック作成・更新・取得

Evernote Service のデータ(ノートブック、ノート、タグ、etc.)を操作(取得、作成、更新、削除)するには NoteStore サービスのクライアントを使う。
手順は以下。

  1. UserStoreクライアントをセットアップ/認証
    -認証されたUserオブジェクトと、認証トークンが渡される
  2. NoteStoreをセットアップ
    -認証されたUserオブジェクトがもつshardIdが必要
  3. NoteStoreサービスのメソッド(プロシージャ)を呼ぶ
    -プロシージャコールの都度、認証トークンをパラメータで渡す

ノートブックを作成・更新・取得するコードの例。
public class NotebookTest {

    private static String consumerKey = "consumer key";
    private static String consumerSecret = "consumer secret";
    private static String evernoteHost = "sandbox.evernote.com";
    private static String userName = "user name";
    private static String password = "password";

    private static NoteStore.Client setupNoteStore(String evernoteHost, User user) throws TException {
        // NoteStore クライアントのセットアップ
        String noteStoreUrl = "https://" + evernoteHost + "/edam/note/" + user.getShardId();
        THttpClient trans = new THttpClient(noteStoreUrl);
        TBinaryProtocol prot = new TBinaryProtocol(trans);
        NoteStore.Client noteStore = new NoteStore.Client(prot, prot);
        return noteStore;
    }
    
    public static void main(String[] args) {
        UserStore.Client userStore = null;
        NoteStore.Client noteStore = null;
        AuthenticationResult authResult = null;
        try {
            userStore = AuthUtil.setupUserStore(evernoteHost);
            authResult = AuthUtil.authenticate(userStore, consumerKey, consumerSecret, evernoteHost, userName, password);
            User user = authResult.getUser();
            String authToken = authResult.getAuthenticationToken();
            
            noteStore = setupNoteStore(evernoteHost, user);
            
            // 新規ノートブック作成
            System.out.println("** CREATE **");
            Notebook notebook1 = new Notebook();
            notebook1.setName("testbook1");
            Notebook created1 = noteStore.createNotebook(authToken, notebook1);
            System.out.println("新規ノートブック\"" + created1.getName() + "\" が作成されました。(guid=" + created1.getGuid() + ")");
            Notebook notebook2 = new Notebook();
            notebook2.setName("testbook2");
            Notebook created2 = noteStore.createNotebook(authToken, notebook2);
            System.out.println("新規ノートブック\"" + created2.getName() + "\" が作成されました。(guid=" + created2.getGuid() + ")");
            
            // ノートブック取得
            System.out.println("** GET **");
            Notebook notebook1_1 = noteStore.getNotebook(authToken, created1.getGuid());
            System.out.println("ノートブック\"" + notebook1_1.getName() + "\" を取得しました。(Update Sequence Number: " + notebook1_1.getUpdateSequenceNum() + ")");
            
            // ノートブック更新
            System.out.println("** UPDATE **");
            notebook1_1.setName("testbook1_xyz");
            int usn = noteStore.updateNotebook(authToken, notebook1_1);
            System.out.println("ノートブック\"" + notebook1_1.getName() + "\" を更新しました。 (Update Sequence Number: " + usn + ")");
            
            // ノートブックを削除
            // XXX デフォルトはで許可されない。(*1)
            // System.out.println("** EXPUNGE **");
            // int guid = noteStore.expungeNotebook(authToken, created2.getGuid());
            // System.out.println("ID: " + guid + " のノートブックが削除されました。");
            
            // 全ノートをリストアップ
            System.out.println("** LIST **");
            List<Notebook> notebooks = noteStore.listNotebooks(authToken);
            for (Notebook notebook : notebooks) {
                System.out.print("- " + notebook.getName());
                if (notebook.isDefaultNotebook()) {
                    System.out.print(" (default)");
                }
                System.out.println("");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
※ AuthUtil は、UserStore初期化・認証用に作成したユーティリティクラス

NoteStoreクライアント作成時(setupNoteStore())に、URLの最後に付けている shardId というのは、API Overview ドキュメントによると

This allows the service to efficiently pass your requests to the appropriate NoteStore shard server.

とあるので、なにかしらサーバーのロケーションを特定するのに使っている?

実行例。
** CREATE **
新規ノートブック"testbook1" が作成されました。(guid=263db0da-edb1-43a2-af0e-bb7491be275d)
新規ノートブック"testbook2" が作成されました。(guid=456ffd44-a71c-4c3e-a297-104465a354b7)
** GET **
ノートブック"testbook1" を取得しました。(Update Sequence Number: 112)
** UPDATE **
ノートブック"testbook1_xyz" を更新しました。 (Update Sequence Number: 114)
** LIST **
- default notebook (default)
- testbook1_xyz
- testbook2

(*1)
NoteStore.Client#expungeNotebook() というメソッドを呼ぶと、認証トークンは正しいはずなのに
EDAMUserException(errorCode:PERMISSION_DENIED, parameter:authenticationToken)
という例外が発生してノートブックが削除できない。
フォーラムを検索してみると、「デフォルトのAPIキーだとCREATE/READ/UPDATEしかできないようにしている、もしexpungeXXXをしたいのなら別途申請して」ということらしい。。
http://forum.evernote.com/phpbb/viewtopic.php?f=43&t=16949&p=67917&hilit=expungenotebook#p67917

Evernote API を使ってみる (2) 認証

Evernote サービスのクライアントを作成して、認証するまで。正しく認証されると、認証トークンがもらえ、そのあとは取得した認証トークンを使ってRPCできるようになる。
具体的には以下の3つのステップを踏む。
  1. Evernote サービスのURLを指定してUserStore.Client というクラスを初期化
  2. バージョンチェック
  3. consumer key, consumer secret, user name, password で認証
細かいコーディングはサンプルを参照すればOK, 一応こんな感じで。

public class AuthTest {

    private static String consumerKey = "consumer key";
    private static String consumerSecret = "consumer secret";
    private static String evernoteHost = "sandbox.evernote.com";
    private static String userName = "user name";
    private static String password = "password";

    private UserStore.Client setupUserStore() throws TException {
        // 1. UserStore クライアントのセットアップ
        String userStoreUrl = "https://" + evernoteHost + "/edam/user";
        THttpClient trans = new THttpClient(userStoreUrl);
        TBinaryProtocol prot = new TBinaryProtocol(trans);
        UserStore.Client userStore = new UserStore.Client(prot);
        
        // 2. バージョンチェック
        boolean versionOk = userStore.checkVersion("Evernote's EDAMDemo (Java)",
                com.evernote.edam.userstore.Constants.EDAM_VERSION_MAJOR,
                com.evernote.edam.userstore.Constants.EDAM_VERSION_MINOR);
        if (!versionOk) {
            System.err.println("Incomatible EDAM client protocol version");
        }
        return userStore;
    }
    
    private boolean authenticate(UserStore.Client userStore) throws EDAMSystemException, TException {
        // 3. ユーザー認証
        AuthenticationResult authResult = null;
        try {
            authResult = userStore.authenticate(userName, password, consumerKey, consumerSecret);
            User user = authResult.getUser(); // 認証されたユーザー
            String authToken = authResult.getAuthenticationToken(); // 認証トークン
            System.out.println("ユーザー  " + user.getName() + " が認証されました。");
            System.out.println("Authentication Token: " + authToken);
        } catch (EDAMUserException ex) {
            // 例外処理
            String parameter = ex.getParameter(); // パラメータ
            EDAMErrorCode errorCode = ex.getErrorCode(); // エラーコード
          
            System.err.println("Authentication failed (parameter: " + parameter + " errorCode: " + errorCode + ")");
            // 認証に失敗した場合
            if (errorCode == EDAMErrorCode.INVALID_AUTH) {
                // consumer key が不正
                if (parameter.equals("consumerKey")) {
                    System.err.println("Your consumer key was not accepted by " + evernoteHost);
                    System.err.println("If you do not have an API Key from Evernote, you can request one from http://www.evernote.com/about/developer/api");
                // user name が不正
                } else if (parameter.equals("username")) {
                    System.err.println("You must authenticate using a username and password from " + evernoteHost);
                // password が不正
                } else if (parameter.equals("password")) {
                    System.err.println("The password that you entered is incorrect");
                }
            }
            return false;
        }
        return true;
    }
    
    public static void main(String[] args) {
        AuthTest test = new AuthTest();
        try {
            UserStore.Client userStore = test.setupUserStore();
            test.authenticate(userStore);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
}

実行して、ユーザ名と認証トークンがコンソールに表示されれば認証成功。
ユーザー XXX が認証されました。
Authentication Token:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Evernote API を使ってみる (1) 準備

Evernote API (for Java) を使うメモ。とりあえず、事前準備&付属サンプルを動かすところまで。Evernoteの知名度からすると日本語ソースが少ない気がするけど、APIの存在は実はあまり知られてないのかな、積極的にPRしている感じもないからだろうか。


APIの入手場所

Evernote API Overview
(Evernoteサイトのフッターにある控えめな "Developers" というリンクを辿った先の "Evernote Service API" というリンク)

準備

使えるようになるまで、若干めんどうな手順がいる。
※当たり前といえば当たり前だけど、Evernote のアカウントが必要。

①APIキーを申請
APIを使うには、キーが必要なのでまずは申請。
上記 Evernote API Overview ページの "Request an API Key" フォームに必要事項を入力して、キーが送られてくるのを待つ。"Application Type"はデスクトップアプリとかモバイルアプリの場合は"Client Application"を、Webアプリの場合は"Web Application"を選ぶ。違いは認証方式で、クライアントアプリは普通にユーザ名/パスワードで認証、Webアプリの場合はOAth認証になる。とりあえず今回はローカルで動かしたいので Client Applicationを選択した。
人手でチェックしているらしく、数日かかる(リクエスト受付けメールによると、プレミアムユーザーなら1日で審査結果?が届くよ、と)とのこと。

辛抱強く待っていると、メールで consumer key と consumer secret が届くので控えておく。

②サンドボックスのアカウントを取得
いきなり本サイト用のデータを読んだり書いたりすることはできなくて、サンドボックスから始めてね、といわれる。プロダクションシステムを使う準備ができたら教えてね、って、連絡手段書いてないなぁ。。

The API key will work against sandbox.evernote.com, but not against www.evernote.com yet.  We can activate it on the production system once you let us know that you're ready to go live.


というわけで、サンドボックス用のサイトにアクセスしてアカウントを登録する。
サンドボックスの見た目や使い方は普通のEvernoteサイトと同じ。

③ドキュメントに目を通す
30ページくらいの概要ドキュメントをざざーっと流し読むとなんとなくアーキテクチャがわかる。途中 Thrift を使ってどうの、とか出てくるけれど、APIを使うぶんには特に詳細を意識する必要はない。サーバーへの接続時のおまじないとして目にするくらい。
(とはいえ、ちょっとだけ調べてみる。Thrift は、クロスラングエッジな RPC フレームワークで、元々はFacebook が開発していて今は Apache Incubator に入っている。Thrift IDL という定義言語で型やサービスを定義して、Thrift にかけると色んな言語用にスタブを生成してくれる、らしい(使ってみたわけではないので表現が間違っているかもしれない)。対応している言語は"C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk, and OCaml"とけっこういろいろ。)

④必要なもの一式をダウンロード
Evernote API Overview から、zipファイルを落としてくる。展開すると、(各種言語の)ライブラリ、ドキュメント、サンプルプログラムが入っている。libフォルダやsampleフォルダの中を見ると、java, perl, php, python, ruby がある様子。


サンプルを動かす

Java の クライアント用サンプル(アカウントの全ノートをリストアップ&画像ファイル付きノートをひとつ作成するプログラム)を動かしてみる。
事前にサンドボックスにいくつかノートを作っておいたほうがいい。
java/client にサンプルプログラムがあるので、その中の consumerKey と consumerSecret を自分のキーに書き換えてやって、実行(lib/java にある jar にクラスパスを通しておく)。引数にサンドボックスのユーザ名/パスワードを渡す。

無事、ノートのリストが出力されて、デフォルトノートブックにノートがひとつ追加されれば成功。

2010年9月6日月曜日

ブログタイトル変更

(本人以外にはどうでもいいエントリです。)

もともと、RoR入門のために作ったブログ "Cocomo on Rails" だったんですが、だんだん内容が雑多になってきたのでタイトルを変更しました。
あと、アカウントがTwitterと違っていたのを地味に統一したのと、ついでにテンプレートを変更。
でもドメインに前タイトルの残骸が、、、><