Dropbox APIを使ってDropboxのフォルダを操作する（調査中）

　とりあえずDropbox APIを使ってみたので今できたところまで記録。調査中ではあるもののいちおう形にはなっているはず。

今回使ったのは以下の環境です。

* Windows 10(2015年11月バージョン)
* PHP7.0.0RC7
* Composer

PHPについては本当はPHP5.6を使おうと思いましたが、x64版をダウンロードしても32bit版として動作してしまうため、以下のサイトよりPHP7.0をダウンロードしました。php.netから入手できるバージョンと多分同じ。

 * http://windows.php.net/downloads/qa/

# 下準備
まず準備としてやることはふたつ

 * Dropbox APIの利用者登録を行う。
 * Composerを使ってDropbox SDKをダウンロードする。

## Dropbox APIの利用者登録を行う。
まずはDropbox APIの利用者登録を行います。Dropboxサイトの「デベロッパー」メニューより、開発者向けページを開き、「My apps＞Create app」よりアプリ登録を行います。

![image](https://qiita-image-store.s3.amazonaws.com/0/14499/7ab9147f-1063-da8d-53d1-c4984fc475dd.png)

今回自分が使ったのは、Dropbox API(Businessではないほう)です。APIタイプを選択すると、アクセスタイプを「App folder」で登録するか、「Full Dropbox」で登録するか選択します（あとで変更することは不可みたい）。

* App folder：登録したアプリは、あらかじめ定められたフォルダ以下にしかアクセスできない（初期状態：/アプリ/[アプリ名]）
* Full Dropbox：登録したアプリは、Dropbox内の全てのフォルダにアクセス可能

そして、最後にアプリ名を決めます。アクセス権が「App folder」の場合、ここで入力した場合は、これがアクセス可能なフォルダ名となります。

今回は、アクセス権として「App folder」を指定しました。アプリ名は仮に「Test」とします。

![image](https://qiita-image-store.s3.amazonaws.com/0/14499/88f20dc8-dc72-c6fc-9022-6c59a4fbe622.png)

登録完了したら、アプリの設定中断にある「Generated access token」より、アクセストークンを生成し、コピーします。一般向けに公開しない（OAuthを利用しない）場合、このアクセストークンを使ってAPIを利用します。

## Composerを使ってDropbox SDKをダウンロードする。

次に、DropboxをPHPから使うために、SDKをダウンロードします。今回自分はComposerを利用していましたので、以下のようなcomposer.jsonを作ります。

```json:composer.json
{
  "name": "アプリ名（多分適当で良い）",
  "require": {
    "dropbox/dropbox-sdk": "1.1.*"
  }
}
```

コマンドプロンプトを開き、以下のコマンドを実行します。Picoと同じですね。

```bat
> curl -sS https://getcomposer.org/installer | php
> php composer.phar install
```
すると、Dropbox SDKがダウンロードされますので、下準備は完了

# とりあえず、いくつかAPIを呼んでみる

というわけでとりあえずいくつかAPIを呼んでみます。PHPファイルを新規作成して、以下のようなコードを配置します。

```php:test.php
<?php 
require_once('vendor/autoload.php');

$dropbox = new \Dropbox\Client(/* アクセストークン */, /* ユーザーエージェント */);
?>
```

\Dropbox\Clientコンストラクタの第一引数に、上でコピーしたアクセストークンを指定します。第二引数は、アプリを識別するための文字列なので、何でもいいようです。

## とりあえず、書き込み。

まずはファイルを書き込みます。上のコードに引き続き、以下の通りにコードを書きます。

```php:test.php
$fp = fopen("test.jpg", 'rb');
$res = $dropbox->uploadFile('/test.jpeg', \Dropbox\WriteMode::add(), $fp);
fclose($fp);
var_dump($res);
```
すると、Dropboxに「/アプリ名/Test/test.jpeg」というファイルが生成されます。uploadFile()メソッドの戻り値には、ファイルのサイズやMIMEタイプ、リビジョンを示すハッシュコードなどの値が配列で格納されます。

## ファイルの読み込み

次にファイルの読み込み。次のような感じです。

```php:test.php
$fp = fopen("test.jpg", 'w+b');
$res = $dropbox->getFile('/test.jpg', $fp);
fclose($fp);
var_dump($res);
```

getFile()メソッドの戻り値は、uploadFile()メソッドと大体同じようなものです。

## フォルダの情報を取得

フォルダに格納されているファイルの一覧や、フォルダに関する情報を取得できます。

```php:test.php
$res = $dropbox->getMetadataWithChildren('/');
var_dump($res);
```
戻り値には配列で情報が格納されているようです。

```json:一部
array(9) {
  ["hash"]=>
  string(32) "<ハッシュ値>"
  ["thumb_exists"]=>
  bool(false)
  ["bytes"]=>
  int(0)
  ["path"]=>
  string(1) "/"
  ["is_dir"]=>
  bool(true)
  ["icon"]=>
  string(6) "folder"
  ["root"]=>
  string(10) "app_folder"
  ["contents"]=>
  array(12) {
    [0]=>
    array(12) {
      ["rev"]=>
      string(10) "163fea2ee6"
      ["thumb_exists"]=>
      bool(false)
      ["path"]=>
      string(12) "/20151002.md"
      ["is_dir"]=>
      bool(false)
      ["client_mtime"]=>
      string(31) "Thu, 01 Oct 2015 08:28:01 +0000"
      ["icon"]=>
      string(15) "page_white_text"
      ["bytes"]=>
      int(648)
      ["modified"]=>
      string(31) "Thu, 19 Nov 2015 13:16:30 +0000"
      ["size"]=>
      string(9) "648 bytes"
      ["root"]=>
      string(10) "app_folder"
      ["mime_type"]=>
      string(24) "application/octet-stream"
      ["revision"]=>
      int(22)
    }
　　・・・
    [11]=>
    array(10) {
      ["bytes"]=>
      int(0)
      ["rev"]=>
      string(10) "513fea2ee6"
      ["revision"]=>
      int(81)
      ["icon"]=>
      string(6) "folder"
      ["path"]=>
      string(8) "/youtube"
      ["is_dir"]=>
      bool(true)
      ["thumb_exists"]=>
      bool(false)
      ["root"]=>
      string(10) "app_folder"
      ["modified"]=>
      string(31) "Thu, 19 Nov 2015 13:16:30 +0000"
      ["size"]=>
      string(7) "0 bytes"
    }
  }
  ["size"]=>
  string(7) "0 bytes"
}
```

とりあえず内容はざっくりこんな感じです。

 * ディレクトリには、ハッシュ値が一つ指定されている。このハッシュ値をgetMetadataWithChildrenIfChanged()メソッドの引数に使うと、そのハッシュ値のリクエスト以降変更された「ディレクトリ内の」ファイルが列挙されます(サブディレクトリでファイルが変更された場合は、たとえハッシュ値の値以降に変更されても、列挙されません)。
 * データのcontents値配下に指定ディレクトリ内の全てのファイル及びディレクトリの情報が格納される。サブディレクトリの情報は列挙されない(ので、どうしてもサブディレクトリを見たい場合は、再帰する必要がある)

# 前の更新から変更があった項目のみを列挙する

いままでのAPIとは別に、前回APIを呼んだときから変更されたファイルのみの一覧を取得する、getDelta()というAPIもあります。

こちらは、APIを呼んだときに返却されるカーソルという文字列を指定することで、直前にAPIが呼ばれた時からの差分のみを取得できるというもののようです(サンプルがDropbox SDKの「dropbox/dropbox-sdk/examples/delta.php」にあります)。

getDelta()ではかなり巨大な配列が得られます。

```json:一部
array(4) {
  ["has_more"]=>
  bool(false)
  ["cursor"]=>
  string(131) "（カーソル文字列）"
  ["entries"]=>
  array(63) {
    [0]=>
    array(2) {
      [0]=>
      string(12) "/20151002.md"
      [1]=>
      array(12) {
        ["rev"]=>
        string(10) "163fea2ee6"
        ["thumb_exists"]=>
        bool(false)
        ["path"]=>
        string(12) "/20151002.md"
        ["is_dir"]=>
        bool(false)
        ["client_mtime"]=>
        string(31) "Thu, 01 Oct 2015 08:28:01 +0000"
        ["icon"]=>
        string(15) "page_white_text"
        ["bytes"]=>
        int(648)
        ["modified"]=>
        string(31) "Thu, 19 Nov 2015 13:16:30 +0000"
        ["size"]=>
        string(9) "648 bytes"
        ["root"]=>
        string(10) "app_folder"
        ["mime_type"]=>
        string(24) "application/octet-stream"
        ["revision"]=>
        int(22)
      }
    }
    ・・・
  }
  ["reset"]=>
  bool(true)
}
```

「entries」要素には、更新されたファイルの一覧が格納されます(配列内の全エントリには、ファイル名とメタデータが格納されている。削除されたファイルはファイル名のみでメタデータがないので、それでファイルが追加されたのか、削除されたのかを判別する。ファイル名の変更は、「古いファイルの削除と新しいファイルの新規作成」という形で表現される)。

また、ドキュメントによると、

 * もし、戻り値のhas_moreパラメータがtrueであった場合は、直ちにカーソルを引数にしてgetDalta()メソッドを呼び出すこと
 * もし、戻り値のresetパラメータがtrueであった場合は、リセットが必要なほど大きな変更があったことを示す(もし、ローカルにファイルをダウンロードするような処理をしていた場合は、いったん削除する必要がある)
 * 初回の呼出し時は、カーソルを指定しないか、nullを指定する(空文字列などではダメ)
 * has_moreがfalseだった場合（つまり正常動作だった場合）、次の動作までには5分待つこと

とのこと。ただ、実験中は割と頻繁にAPI呼んでも平気だったので、5分というのがどういう意味なのかはよくわかりません。

## Webhook
なお、Dropboxではファイルが更新されたときにあらかじめ指定したURLを呼び出す、Webhookにも対応しています。APIコンソール画面下の、「Webhooks」より任意のURLを指定することが可能。

こちらの使い方は、SlackのIncoming Webhook等と比べるとちょっと複雑です。

### verification requestを受ける

まず、APIコンソールのWebhooksの項目よりURLを指定したときに行われる、Webhooksに対応します。これは、GETリクエストで送信されてくるchallengeをそのまま返すだけ というごく簡単な処理だけを実装すればいいです。
つまり、以下のような感じ。

```php:webhook.php
<?php
  echo htmlspecialchars($_GET['challenge']);
?>
```
これだけ。

### 以降のWebhookを受ける
verification requestを完了すると、次のフォルダ内容変更時から、以下のようなHTTPリクエストがPOSTされるようになります。

 * リクエストヘッダに、「X-Dropbox-Signature」が含まれる(HMAC-SHA256のシグネチャだそうです。よくわからない)。
 * ボディに、更新に関する情報を格納したJSONデータ。

JSONデータは、以下のような感じになっています(Dropbox APIのドキュメントより引用)

```json:送られてくるJSONデータ
{
    "list_folder": {
        "accounts": [
            "dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc",
            ...
        ]
    },
    "delta": {
        "users": [
            12345678,
            23456789,
            ...
        ]
    }
}
```

上のlist_folder/accountsという情報については、このフォルダを「誰が」変更したのかという情報。更新した人のIDが格納されている(らしい)。
次のdelta/usersという情報については、更新を一括りにしたdeltaという値が格納されています。バージョン履歴を見るときに、キーとなる数字ですので、この数値を使えばファイルのバージョンが一位に取得できます。これは、他のAPIでドキュメントのバージョンを指定する場合に必要・・・ なようなのですが、自分が見た限り、このDeltaを引数として要求するAPIがありませんので、下のようにURLに指定してブラウザで開くのに使える程度かもしれません（？）。

![image](https://qiita-image-store.s3.amazonaws.com/0/14499/9b97ca70-978e-528b-9235-6eb069c10438.png)

なお、Webhooksのレスポンスは共に、10秒以内に行う必要があるようです。それを超えるとエラーになるようなので注意。

# 参考にしたサイト

 * http://blog.fkoji.com/2015/01232356.html (今のDropbox APIとは提供方式が違うので、利用申請の書き方は違います)
 * http://www.petitec.com/2014/05/dropbox-api-command-by-webook-on-sakura/
 * [Dropbox API Webhookマニュアル](https://www.dropbox.com/developers/reference/webhooks)
 * [Dropbox SDK PHP版仕様書](http://dropbox.github.io/dropbox-sdk-php/api-docs/v1.1.x/class-Dropbox.Client.html)