酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Observable.FromAsyncPattern "SharpBox" sample for Windows Phone 7

時代はクラウド!ということもあり、スマートフォンアプリケーションではもはや定番となっているTwitterFacebook対応に続いて、EvernoteやDropboxに対応するアプリが続々と出てきています。

Windows Phoneでは、TwitterやFacebookへの投稿は標準APIとして用意されていますが、クラウドストレージ方面の対応がまだ弱い印象を受けます。

イチからライブラリを作るのは大変なので、「SharpBox」というライブラリを使わせてもらいましょう。DropBoxの認証を行い、ディレクトリを作ったり、ファイルをアップロード、ダウンロードさせる方法についてご紹介したいと思います。

  • (追記 9/5 1:35)
    • リビジョン79364でDropBoxからダウンロードも可能な事を確認しました。
  • (追記 9/5 1:15)
    • 最新のリビジョン79364からAppLimit.CloudComputing.SharpBox.WP7.dllを使ったところ、問題なくDropBoxへのアップロードが実施出来ることを確認しました。
  • (追記 9/4 18:50)
    • SharpBox 1.1 SR1では、アップロードやダウンロードに対応していないことがわかりました。リポジトリの最新のログを見ていると、Windows Phone 7の不具合修正とあるので、最新を取得してビルドしてみると上手くいきそうです。どなたか情報をください
  • (追記 2012/12/27)Dropboxの仕様が変更されてしまい、このTipsは既に部分的に使えなくなっています。ご注意ください。

SharpBoxのライブラリをダウンロードする

Windows Phone用に使うことを考えているのであれば、リリースビルドのSharpBox v1.1 SR1は使用するべきではありません。最新のリビジョンのものを使うべきでしょう。

http://sharpbox.codeplex.com/SourceControl/list/changesets

本記事のサンプルコードは、リビジョン79364で動作出来ることを確認しております。Latest Vesionのソースコードをダウンロードしてきてビルドしたdllを、使いたいプロジェクトへ参照の追加してください。

DropBoxの認証を行いディレクトリを作成する

このライブラリは、一応Windows Phone 7対応はされていますが、まだ完全にWindows Phone 7対応がなされていないらしく、標準ではIObservableへの変換が難しいので拡張メソッドを作って対応しています。本記事の最後の方に掲載しています。

DropBoxCredentialsクラスのインスタンスを生成して、認証用の鍵と自分のアカウント情報を設定します。拡張メソッドのCloudStorage.GetOpenRequestObservableを使い、SubscribeでDropBoxのルート直下に「BouTokei」というディレクトリを作成しています。

    // DropBox用の認証オブジェクト
    var credentials = new DropBoxCredentials();

    // アプリID申請時にもらったキーを入力する
    credentials.ConsumerKey = "XXXXXXXXXXXXXXX";
    credentials.ConsumerSecret = "YYYYYYYYYYYYYY";

    // 自分のアカウント情報
    credentials.UserName = "ch3cooh@example.com";
    credentials.Password = "my_dropbox_password";

    // 認証後、ルート直下に「BouTokei」ディレクトリを作成する
    var configuration = DropBoxConfiguration.GetStandardConfiguration();
    var storage = new CloudStorage();
    storage.GetOpenRequestObservable(configuration, credentials)
        .Subscribe(token => {

            // DropBoxのルートにBouTokeiディレクトリを作成
            var dir = storage.CreateFolder("BouTokei", null);
            if (dir == null) {
                Debug.WriteLine("Couldn't create Folder");
                return;
            }

        }, ex => MessageBox.Show(ex.Message) );

注意点です。CloudStorage.CreateFolderメソッドは、絶対にUIスレッドで実行しないでください。内部的にAutoResetEventが使用されているのかフリーズします(→HttpWebRequestで同期的に通信を行うことは出来るのか?)。

上記のサンプルコードで例えると、ObserveOnDispatcherメソッドの後のSubscribeで、CloudStorage.CreateFolderメソッドを実行するとフリーズします。気をつけてくださいね。

DropBoxの認証を行いディレクトリを作成し、ファイルをアップロードする

SharpBox 1.1 SR1ではNotSupportedExceptionが発生しますが、リビジョン79364以降だと問題なくアップロード出来ることを確認しています。一応検証に使っていたサンプルコードを貼っておきます。

    // DropBox用の認証オブジェクト
    var credentials = new DropBoxCredentials();

    // アプリID申請時にもらったキーを入力する
    credentials.ConsumerKey = "XXXXXXXXXXXXXXX";
    credentials.ConsumerSecret = "YYYYYYYYYYYYYY";

    // 自分のアカウント情報
    credentials.UserName = "ch3cooh@example.com";
    credentials.Password = "my_dropbox_password";

    // 認証後、ルート直下に「BouTokei」ディレクトリを作成する
    var configuration = DropBoxConfiguration.GetStandardConfiguration();
    var storage = new CloudStorage();
    storage.GetOpenRequestObservable(configuration, credentials)
        .Subscribe(token => {

            // アクセストークンを安全にシリアライズ
            using (var strm = storage.SerializeSecurityToken(token)) {
                strm.WriteToIsolatedStorage("access_token.dat");
            }

            // DropBoxのルートにBouTokeiディレクトリを作成
            var dir = storage.CreateFolder("BouTokei", null);
            if (dir == null) {
                Debug.WriteLine("Couldn't create Folder");
                return;
            }

            // ファイルをアップロードする
            var file = storage.CreateFile(dir, "gazou.jpg");
            if (file == null) {
                Debug.WriteLine("Couldn't create File");
                return;
            }

            // 転送オブジェクトを取得する
            var transfer = file.GetDataTransferAccessor();
            if (transfer == null) {
                Debug.WriteLine("Couldn't create File");
                return;
            }

            Stream writer = new MemoryStream();
            using (var store = IsolatedStorageFile.GetUserStoreForApplication())
            using (var reader = store.OpenFile("Shared/ShellContent/bijintokei_tile.jpg", FileMode.Open)) {
                // アップロード用のストリームに情報をコピー
                reader.WriteTo(writer);
                writer.Position = 0;
            }

            // 転送開始
            transfer.Transfer(writer, nTransferDirection.nUpload, (se, ex) => {
                Debug.WriteLine("uploading... {0}/{1}", ex.TransferRateCurrent, ex.TransferRateTotal);
            }, null);

        }, ex => MessageBox.Show(ex.Message) );

SharpBoxの現行バージョン(v1.1 SR1)では、フォルダ作成までしか対応していないみたいですね。内部で使われている GetFilesystemObjectUrl APIが問題で、そのままではWindows Phone 7向けでアップロード/ダウンロードのサポートをおこなうと問題発生すると書かれています。

A word to all Windows Phone 7 developers: Currently SharpBox is doing well for metadata browsing but the up-/download capabilities are not working well. There are several hacks for this via our API GetFilesystemObjectUrl. Any feedback and help to solve the WP7 problems is welcome.

SharpBox 1.1 SR1

リポジトリのログを見る限りでは、「Windows Phone 7のダウンロード処理を修正」と書かれているので、headを取ってきて自前でビルドをおこなうと幸せになれるかもしれません。お試しください。

リビジョン79364で動作確認をおこなったところ、Dropboxへファイルをアップロードすることが可能なのを確認しています。

DropBoxの認証を行いディレクトリを作成し、ファイルをダウンロードする

アップロードの件と同様に、v1.1 SR1ではNotSupportedExceptionが発生します。リビジョン79364で実行したところコード自体はアップロードとほぼ同じでいけるはずです。

下記のサンプルコードは、既にDropBoxの同期済みの「BouTokei/gazou.jpg」をダウンロードして、分離ストレージの「dropbox.jpg」へ格納しています。

    // DropBox用の認証オブジェクト
    var credentials = new DropBoxCredentials();

    // アプリID申請時にもらったキーを入力する
    credentials.ConsumerKey = "XXXXXXXXXXXXXXX";
    credentials.ConsumerSecret = "YYYYYYYYYYYYYY";

    // 自分のアカウント情報
    credentials.UserName = "ch3cooh@example.com";
    credentials.Password = "my_dropbox_password";

    // 認証後、ルート直下に「BouTokei」ディレクトリを作成する
    var configuration = DropBoxConfiguration.GetStandardConfiguration();
    var storage = new CloudStorage();
    storage.GetOpenRequestObservable(configuration, credentials)
        .Subscribe(token => {

            // アクセストークンを安全にシリアライズ
            using (var strm = storage.SerializeSecurityToken(token)) {
                strm.WriteToIsolatedStorage("access_token.dat");
            }

            // DropBoxのルートにBouTokeiディレクトリを作成
            var dir = storage.CreateFolder("BouTokei", null);
            if (dir == null) {
                Debug.WriteLine("Couldn't create Folder");
                return;
            }

            // ファイルのダウンロード
            Stream reader = new MemoryStream();
            storage.DownloadFile("gazou.jpg", dir, reader);

            // ダウンロードしてきたストリームを分離ストレージへ書き出す
            using (var store = IsolatedStorageFile.GetUserStoreForApplication())
            using (var writer = store.CreateFile("dropbox.jpg")) {
                reader.Position = 0;
                reader.WriteTo(writer);
                writer.Flush();
            }

            reader.Close();
            reader.Dispose();

        }, ex => MessageBox.Show(ex.Message) );

CloudStorage用の拡張メソッド

BeginOpenRequestメソッドのオーバーロードにBeginOpenRequest( ICloudStorageConfiguration, ICloudStorageCredentials, AsyncCallback, Object)が無かった為、Observable.FromAsyncPatternでIO化が出来ませんでした。

BeginOpenRequestメソッドをラッピングした上で、IObservableを作成するメソッドを用意しました。これでDropBoxの認証処理部分が、Rxで簡単に扱えるようになってたと思います。

    public static class CloudStorageExtensions {

        /// <summary>
        /// 認証を行いストレージへ接続を行う
        /// </summary>
        /// <remarks>
        /// CloudStorageにBeginOpenRequest(ICloudStorageConfiguration,ICloudStorageCredentials,AsyncCallback,Object)が
        /// 無かった為、Observable.FromAsyncPatternでIO化することが出来なかったのでラッピングメソッドを用意
        /// </remarks>
        /// <param name="storage"></param>
        /// <param name="configuration"></param>
        /// <param name="credentials"></param>
        /// <param name="callback"></param>
        /// <param name="dummy"></param>
        /// <returns></returns>
        public static IAsyncResult BeginOpenRequest(this CloudStorage storage, 
            AppLimit.CloudComputing.SharpBox.ICloudStorageConfiguration configuration,
            AppLimit.CloudComputing.SharpBox.ICloudStorageCredentials credentials, 	
            AsyncCallback callback, Object dummy) {

                return storage.BeginOpenRequest(callback, configuration, credentials);
        }

        /// <summary>
        /// 認証を行いストレージへ接続を行う
        /// </summary>
        /// <remarks>
        /// CloudStorageにBeginOpenRequest(ICloudStorageConfiguration,ICloudStorageAccessToken,AsyncCallback,Object)が
        /// 無かった為、Observable.FromAsyncPatternでIO化することが出来なかったのでラッピングメソッドを用意
        /// </remarks>
        /// <param name="storage"></param>
        /// <param name="configuration"></param>
        /// <param name="credentials"></param>
        /// <param name="callback"></param>
        /// <param name="dummy"></param>
        /// <returns></returns>
        public static IAsyncResult BeginOpenRequest(this CloudStorage storage,
            AppLimit.CloudComputing.SharpBox.ICloudStorageConfiguration configuration,
            AppLimit.CloudComputing.SharpBox.ICloudStorageAccessToken accressToken,
            AsyncCallback callback, Object dummy) {
                return storage.BeginOpenRequest(callback, configuration, accressToken);
        }

        /// <summary>
        /// 認証を行いストレージへ接続を行う
        /// </summary>
        /// <remarks>
        /// CloudStorageにBeginGetChildsRequest(ICloudDirectoryEntry,AsyncCallback,Object)が
        /// 無かった為、Observable.FromAsyncPatternでIO化することが出来なかったのでラッピングメソッドを用意
        /// </remarks>
        /// <param name="storage"></param>
        /// <param name="dirEntry"></param>
        /// <param name="callback"></param>
        /// <param name="dummy"></param>
        /// <returns></returns>
        public static IAsyncResult BeginGetChildsRequest(this CloudStorage storage, AppLimit.CloudComputing.SharpBox.ICloudDirectoryEntry dirEntry,
            AsyncCallback callback, Object dummy) {    
                return storage.BeginGetChildsRequest(callback, dirEntry);
        }

        public static IObservable<ICloudStorageAccessToken> GetOpenRequestObservable(
            this AppLimit.CloudComputing.SharpBox.CloudStorage storage,
            AppLimit.CloudComputing.SharpBox.ICloudStorageConfiguration configuration,
            AppLimit.CloudComputing.SharpBox.ICloudStorageCredentials credentials) {

            return Observable.FromAsyncPattern<
                AppLimit.CloudComputing.SharpBox.ICloudStorageConfiguration,
                AppLimit.CloudComputing.SharpBox.ICloudStorageCredentials,
                AppLimit.CloudComputing.SharpBox.ICloudStorageAccessToken>(
                storage.BeginOpenRequest, storage.EndOpenRequest)(configuration, credentials);
        }

        public static IObservable<ICloudStorageAccessToken> GetOpenRequestObservable(
            this AppLimit.CloudComputing.SharpBox.CloudStorage storage,
            AppLimit.CloudComputing.SharpBox.ICloudStorageConfiguration configuration,
            AppLimit.CloudComputing.SharpBox.ICloudStorageAccessToken accessToken) {

            return Observable.FromAsyncPattern<
                AppLimit.CloudComputing.SharpBox.ICloudStorageConfiguration,
                AppLimit.CloudComputing.SharpBox.ICloudStorageAccessToken,
                AppLimit.CloudComputing.SharpBox.ICloudStorageAccessToken>(
                storage.BeginOpenRequest, storage.EndOpenRequest)(configuration, accessToken);
        }

        public static IObservable<List<ICloudFileSystemEntry>> GetChildsRequestObservable(
            this AppLimit.CloudComputing.SharpBox.CloudStorage storage,
            AppLimit.CloudComputing.SharpBox.ICloudDirectoryEntry dirEntry) {
        
            return Observable.FromAsyncPattern<
                AppLimit.CloudComputing.SharpBox.ICloudDirectoryEntry,
                List<AppLimit.CloudComputing.SharpBox.ICloudFileSystemEntry>>(
                storage.BeginGetChildsRequest, storage.EndGetChildsRequest)(dirEntry);
        }

    }