Google Apps ScriptでGoogle Driveのフォルダ構造を含んだファイルリストを作成する

モノを作成しているとき、Google Apps Script (GAS)を使用する箇所でも時々フォルダやファイルのリストが必要になる場合があります。ドライブ内の特定のフォルダ下のファイルリストやフォルダ構造も必要になる場合があります。これまではその都度用意していたのですが、このような状況が増えてきたことから、モノを作る際に少しでも手間を省こうと考えてここで紹介させていただくライブラリを作成しました。また、最近、フォルダ構造の情報を含んだファイルリストの取得に関する問い合わせが増えていることもあり、他の方々に対してもお役に立てることができればと思い公開させていただきました。

ライブラリの本体は[GitHub](https://github.com/tanaikech/FilesApp)でご覧いただけます。

（更新 2020年5月14日）マイドライブと共有ドライブの両方からファイルリストが取得できるようになりました。

# ライブラリのプロジェクトキー
~~~
1dBxqh6gpWqFv4h0M25ALp4bhcs6CoqhiIJ0fUeUjR57bAxIN2PqfkQns
~~~

# インストール方法
1. 最初にスクリプトディタを立ち上げてライブラリをインストールします。インストール方法は[こちら](https://developers.google.com/apps-script/guides/libraries)をご覧ください。
    - このとき使用するライブラリのプロジェクトキーは、**``1dBxqh6gpWqFv4h0M25ALp4bhcs6CoqhiIJ0fUeUjR57bAxIN2PqfkQns``**です。
1. 次にAPIコンソールでDrive APIを有効にします。
    - スクリプトエディタの「リソース」 -> 「Cloud Platformプロジェクト」を開きます。
    - 「このスクリプトが現在関連付けられているプロジェクト:」の下のリンクをクリックします。
    - 「スタートガイド」の「API を有効化し、鍵などの認証情報を取得」をクリックします。
    - ダッシュボードにDrive APIが見えている場合はそれをクリックします。見えていない場合は、左側のサイドバーにある「ライブラリ」をクリックします。
    - 「APIとサービスを検索」へ Drive API と入力して検索します。
    - 表示された中からDrive APIをクリックします。
    - ここでDrive APIを有効にします。ボタンをクリックしてから少しの待ち時間があります。

（注意）少し前はライブラリの中にDriveAppを使用する箇所があればスクリプトエディタによって自動でDrive APIが有効になっていたのですが、最近仕様が変わったようでライブラリ内のDriveAppは無視されるようになったため、手動で設定する必要があります。

# 使い方
用意したメソッドは次の4つです。今のところ、自分で使用している状況と問い合わせで多かった項目を合わせて4種類を用意しました。

| メソッド | 内容 |
|:-----|:-----|
| createTree(folderId, mimeType, fields) | folderIdで指定したフォルダ下の全階層のファイル、フォルダリストを構造付きで取得 |
| getAllInFolder(folderId, mimeType, fields) | folderIdで指定したフォルダ下の全ファイルを構造は無視して取得 |
| getAllFoldersInFolder(folderId) | folderIdで指定したフォルダ下の全階層のフォルダリストを取得 |
| getFilesAndFoldersInFolder(folderId, mimeType, fields) | folderIdで指定したフォルダ直下の全ファイルを取得 |

- **fileId** : 検索する際のトップフォルダのIDです。例えばこれを"root"とすると、全てのファイルやフォルダを取得することができます。
- **mimeType** : ファイルを取得する際のmimeTypeをここで指定します。1次元配列として、例えば``["application/vnd.google-apps.script", "image/png"]``のように与えてください。また、**mimeTypeをnullにすることで全ての種類のファイルを取得します。**
- **fields** : [Drive APIのfiles.list](https://developers.google.com/drive/api/v3/reference/files/list)で使用するfieldsと同じものを使用することができます。例えばファイル名のみを取得したい場合は、``"files(id,name)"``を与えてください。

# サンプル
サンプルとして下図のようなフォルダ構造を想定して各メソッドの使い方とそのレスポンスについて記載します。

![fig1.png](https://qiita-image-store.s3.amazonaws.com/0/133383/da4f5dfe-451d-5e93-3813-39f348e9c9e5.png)


## 1. createTree(folderId, mimeType, fields)
folderId, mimeType, fields へそれぞれ"sampleFolder1"のID, null, files(name) を与えます。

~~~javascript
var res = FilesApp.createTree(folderId, null, "files(name)");
~~~

##### レスポンス
folderIdで与えたフォルダ以下の全てのファイル、フォルダを取得するとともに、それぞれのファイルがフォルダ構造のどこにあるかの情報も同時に取得します。また、``folderTreeById``と``folderTreeByName``の配列のインデックスはフォルダ構造の深さに対応しています。値が大きくなるほど深いことを意味します。

~~~json
{
  "topFolderId": ["### FolderId of sampleFolder1 ###"],
  "topFolderName": ["sampleFolder1"],
  "totalFilesAndFolders": 11,
  "totalFiles": 4,
  "totalFolders": 7,
  "files": [
    {
      "folderTreeById": ["### FolderId of sampleFolder1 ###"],
      "folderTreeByName": ["sampleFolder1"],
      "filesInFolder": [
        {
          "name": "Spreadsheet1",
          "parents": ["### FolderId of sampleFolder1 ###"]
        },
        {
          "name": "sampleFolder_2b",
          "parents": ["### FolderId of sampleFolder1 ###"]
        },
        {
          "name": "sampleFolder_2a",
          "parents": ["### FolderId of sampleFolder1 ###"]
        }
      ]
    },
    {
      "folderTreeById": ["### FolderId of sampleFolder1 ###", "### FolderId of sampleFolder_2b ###"],
      "folderTreeByName": ["sampleFolder1", "sampleFolder_2b"],
      "filesInFolder": [
        {
          "name": "Spreadsheet4",
          "parents": ["### FolderId of sampleFolder_2b ###"]
        },
        {
          "name": "sampleFolder_2b_3b",
          "parents": ["### FolderId of sampleFolder_2b ###"]
        },
        {
          "name": "sampleFolder_2b_3a",
          "parents": ["### FolderId of sampleFolder_2b ###"]
        }
      ]
    },
    {
      "folderTreeById": ["### FolderId of sampleFolder1 ###", "### FolderId of sampleFolder_2b ###", "### FolderId of sampleFolder_2b_3b ###"],
      "folderTreeByName": ["sampleFolder1", "sampleFolder_2b", "sampleFolder_2b_3b"],
      "filesInFolder": [
        {
          "name": "sampleFolder_2b_3b_4a",
          "parents": ["### FolderId of sampleFolder_2b_3b ###"]
        }
      ]
    },
    {
      "folderTreeById": ["### FolderId of sampleFolder1 ###", "### FolderId of sampleFolder_2b ###", "### FolderId of sampleFolder_2b_3b ###", "### FolderId of sampleFolder_2b_3a ###"],
      "folderTreeByName": ["sampleFolder1", "sampleFolder_2b", "sampleFolder_2b_3b", "sampleFolder_2b_3b_4a"],
      "filesInFolder": [
        {
          "name": "Spreadsheet5",
          "parents": ["### FolderId of sampleFolder_2b_3a ###"]
        }
      ]
    },
    {
      "folderTreeById": [ "### FolderId of sampleFolder1 ###", "### FolderId of sampleFolder_2b ###", "### FolderId of sampleFolder_2b_3a ###"],
      "folderTreeByName": ["sampleFolder1", "sampleFolder_2b", "sampleFolder_2b_3a"],
      "filesInFolder": [
        {
          "name": "Spreadsheet3",
          "parents": ["### FolderId of sampleFolder_2b_3a ###"]
        }
      ]
    },
    {
      "folderTreeById": ["### FolderId of sampleFolder1 ###", "### FolderId of sampleFolder_2a ###"],
      "folderTreeByName": ["sampleFolder1", "sampleFolder_2a"],
      "filesInFolder": [
        {
          "name": "Spreadsheet2",
          "parents": ["### FolderId of sampleFolder_2a ###"]
        },
        {
          "name": "sampleFolder_2a_3a",
          "parents": ["### FolderId of sampleFolder_2a ###"]
        }
      ]
    },
    {
      "folderTreeById": ["### FolderId of sampleFolder1 ###", "### FolderId of sampleFolder_2a ###", "### FolderId of sampleFolder_2a_3a ###"],
      "folderTreeByName": ["sampleFolder1", "sampleFolder_2a", "sampleFolder_2a_3a"],
      "filesInFolder": []
    }
  ]
}
~~~

## 2. getAllInFolder(folderId, mimeType, fields)

~~~javascript
var res = FilesApp.getAllInFolder(folderId, null, "files(name)");
~~~

##### レスポンス
folderIdで与えたフォルダ以下の全てのファイル、フォルダを取得します。このメソッドではフォルダ構造は無視して取得します。このため、速度はcreateTree()よりも少し速いです。

~~~json
[
    {"name":"Spreadsheet5"},
    {"name":"Spreadsheet4"},
    {"name":"Spreadsheet3"},
    {"name":"Spreadsheet2"},
    {"name":"Spreadsheet1"},
    {"name":"sampleFolder_2b_3b_4a"},
    {"name":"sampleFolder_2a_3a"},
    {"name":"sampleFolder_2b"},
    {"name":"sampleFolder_2a"},
    {"name":"sampleFolder_2b_3b"},
    {"name":"sampleFolder_2b_3a"}
]
~~~

## 3. getAllFoldersInFolder(folderId)

~~~javascript
var res = FilesApp.getAllFoldersInFolder(folderId);
~~~

##### レスポンス
folderIdで与えたフォルダ以下の全てのフォルダをフォルダ構造の情報を含めて取得します。

~~~json
{
    "id":[
        ["### ID of sampleFolder1 ###"],
        ["### ID of sampleFolder1 ###","### ID of sampleFolder_2b ###"],
        ["### ID of sampleFolder1 ###","### ID of sampleFolder_2b ###","### ID of sampleFolder_2b_3b ###"],
        ["### ID of sampleFolder1 ###","### ID of sampleFolder_2b ###","### ID of sampleFolder_2b_3b ###","### ID of sampleFolder_2b_3b_4a ###"],
        ["### ID of sampleFolder1 ###","### ID of sampleFolder_2b ###","### ID of sampleFolder_2b_3a ###"],
        ["### ID of sampleFolder1 ###","### ID of sampleFolder_2a ###"],
        ["### ID of sampleFolder1 ###","### ID of sampleFolder_2a ###","### ID of sampleFolder_2a_3a ###"]
    ],
    "name":[
        ["sampleFolder1"],
        ["sampleFolder1","sampleFolder_2b"],
        ["sampleFolder1","sampleFolder_2b","sampleFolder_2b_3b"],
        ["sampleFolder1","sampleFolder_2b","sampleFolder_2b_3b","sampleFolder_2b_3b_4a"],
        ["sampleFolder1","sampleFolder_2b","sampleFolder_2b_3a"],
        ["sampleFolder1","sampleFolder_2a"],
        ["sampleFolder1","sampleFolder_2a","sampleFolder_2a_3a"]
    ]
}
~~~


## 4. getFilesAndFoldersInFolder(folderId, mimeType, fields)

~~~javascript
var res = FilesApp.getFilesAndFoldersInFolder(folderId, null, "files(name)");
~~~

##### レスポンス
folderIdで与えたフォルダの直下にある全てのファイル、フォルダを取得します。この場合、全階層ではありません。

~~~json
[
    {"name":"Spreadsheet1"},
    {"name":"sampleFolder_2b"},
    {"name":"sampleFolder_2a"}
]
~~~

# 工夫したところ
与えたフォルダ下の全階層のフォルダ構造を取得した後、このリストを使って全ファイルを構造付きで取得する際の方法について少し迷いました。まず、ファイル数が多くなると理由は不明ですがDriveAppでは速度が非常に遅くなりましたのでDrive APIを直接使用することにしました。迷った部分は次の2点です。

- Drive APIのfiles.listを使ってDrive内の全ファイルを取得した後にフォルダリストを使って各フォルダ下のファイルを取得する。
- files.listのqを使ってフォルダリストにあるフォルダ内のファイルだけを取得する。

結果として後者を選択しました。理由は、実際に使用する場合を想定すると、全ファイルを検索する状況よりも、ある特定のフォルダ下の情報を取得する場合が多かったからです。例えば、前者の場合、指定したフォルダにファイルが１つしかなくても、全ファイルを読み込むことになり余計なAPIと時間のロスが発生してしまいます。一方、ルートから全ファイルを取得する場合では前者の方がAPI消費量として効率は高いです。しかし、この不利については[バッチリクエスト](https://developers.google.com/drive/api/v3/batch)を採用することで後者でもより少ないAPIと時間のロスを実現できたことで解決しました。