RetrofitでMediaStoreから取得したオブジェクトをアップロードする

Retrofit 1.9.0でファイルをアップロードするには`TypedFile`を使うのが手っ取り早い。以下は最小限の定義。

```java
public interface UploadService {
    @Multipart
    @POST("/upload")
    Observable<Response> upload(@Part("file") TypedFile file);
}
```

`TypedFile`が扱えるのはローカルファイルのみであるが、Android開発をしていてMediaStoreから取得したオブジェクトをアップロードしたいことがあったので以下のようなクラスを作成した。

```java
public class Media implements TypedInput, TypedOutput {
    private static final int BUFFER_SIZE = 4096;

    private Context mContext;
    private Uri mUri;
    private String mDisplayName;
    private String mMimeType;
    private long mSize;

    public Media(Context context, Uri uri) {
        mContext = context;
        mUri = uri;

        ContentResolver contentResolver = mContext.getContentResolver();
        mMimeType = contentResolver.getType(uri);

        Cursor cursor = contentResolver.query(uri, null, null, null, null);
        int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
        cursor.moveToFirst();
        mDisplayName = cursor.getString(nameIndex);
        mSize = cursor.getLong(sizeIndex);
        cursor.close();

        // OpenableColumns.SIZE で取得したサイズは実際のバイト数より大きいことがあり
        // unexpected end of streamになる可能性があるので
        // statの値が利用できればそれを使う
        long statSize = getStatSize(contentResolver, uri);
        if (statSize > 0) {
            mSize = statSize;
        }
    }

    /**
     * Stack Overflowの記事を参考にした
     * @link http://stackoverflow.com/questions/21882322/how-to-correctly-get-the-file-size-of-an-android-net-uri
     */
    private long getStatSize(ContentResolver contentResolver, Uri uri) {
        long size = -1;
        try {
            ParcelFileDescriptor fd = contentResolver.openFileDescriptor(uri, "r");
            size = fd.getStatSize();
            try {
                fd.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return size;
    }

    @Override
    public String fileName() {
        return mDisplayName;
    }

    @Override
    public String mimeType() {
        return mMimeType;
    }

    @Override
    public long length() {
        return mSize;
    }

    @Override
    public InputStream in() throws IOException {
        return mContext.getContentResolver().openInputStream(mUri);
    }

    @Override
    public void writeTo(OutputStream out) throws IOException {
        byte[] buffer = new byte[BUFFER_SIZE];
        InputStream in = in();
        try {
            int read;
            while ((read = in.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
        } finally {
            in.close();
        }
    }
}
```

嵌まったポイントはコメントにも書いている通り`OpenableColumns.SIZE`が正確なサイズを返さない場合があることで、Stack Overflowの [How to correctly get the file size of an android.net.Uri?](http://stackoverflow.com/questions/21882322/how-to-correctly-get-the-file-size-of-an-android-net-uri) に書いてある実装を使わせていただいた。

あとはメソッドの定義を追加するだけ。

```java
public interface UploadService {
    @Multipart
    @POST("/upload")
    Observable<Response> upload(@Part("file") TypedFile file);

    @Multipart
    @POST("/upload")
    Observable<Response> upload(@Part("file") Media media);
}
```