Google Apps Script 実践メモ（Googleドライブ）

先日、仕事で必要に迫られ、Googleフォームからスプレッドシート周りをごにょごにょGoogle Apps Scriptで開発しました。
シリーズの[初回](http://qiita.com/nurburg/items/f6d220e9bd6b11750b17)はForm Submitについて、[第二弾](http://qiita.com/nurburg/items/744ec53477f4ae328555)はスプレッドシート周りのあれこれをまとめました。第三弾は、Googleドライブまわりの処理についてまとめます。

# Googleドライブ(Drive)
## フォルダの存在チェックして新規作成やファイルの追加削除をしたい（権限もね）
ユーザーごとのスプレッドシートが多数あったとして、そこに適切な閲覧権限をつけていく必要がありました。
ただ、閲覧権限をつけた人にURLのリストを渡すのもスマートではないので、閲覧者ごとのフォルダを作って、閲覧者には「あなたのフォルダはこれですよ。そんなかに入っているのがあなたが見れるスプレッドシートなんであとはよろしく」ってことにしようと考えました。

Googleドライブのごく自然な発想だと思いますが、Apps Scriptで組むのは、画面で操作するのとちょっとイメージが異なるので、その辺を含めて書きたいと思います。

### 任意のフォルダを開く
[スプレッドシート](http://qiita.com/nurburg/items/744ec53477f4ae328555)の中でもサラっと触れましたが、任意のフォルダオブジェクトを取得するには、DriveApp.getFolderById(id)とやります。（ULR指定のメソッドはありません）

```js
function myFunction(){
  //保存先フォルダオブジェクトの取得
  var destfolderid = "*******";       //destination folderを略してます
  var destfolder = DriveApp.getFolderById(destfolderid);
}
```
>DriveAppクラスのドキュメントは[こちら](https://developers.google.com/apps-script/reference/drive/drive-app)です。

### 子フォルダを取得する
任意のフォルダの下に、スプレッドシートの閲覧者ごとにフォルダを作成するのですが、その際に当然存在チェックを挟んでいきます。そのために、子フォルダの情報をすべて取得する必要がありました。

サブフォルダをすべて取得するには、フォルダオブジェクトに対してgetFolders()とします。
戻り値はFolderIteratorですので、チェックにはhasNext()やnext()を使用します。

```js
my function(){
  //前述のフォルダオブジェクトdestfolderをそのまま使用します
  //destfolderの子フォルダをすべて取得
  var destfolder_childs = destfolder.getFolders();

  //例えば、文字列"hoge"がフォルダ名に含まれる場合に何か処理する
  while ( destfolder_childs.hasNext() ){
    var folder = destfolder_childs.next();
    if ( folder.getName().indexOf("hoge") != -1 ){
      //処理
    }
  }
}
```
>* Folderクラスのドキュメントは[こちら](https://developers.google.com/apps-script/reference/drive/folder)です。
* そして、 __Folderクラスは、DriveAppで取得したフォルダやファイルしか扱えない__ ので注意が必要です。つまり、SpreadsheetApp.openByUrl(url)とかで取得したスプレッドシートオブジェクトは使えません。

### フォルダの作成、権限追加・削除
フォルダの作成は、作成したい親フォルダオブジェクトにてcreateFolder(name)とします。
権限は同様に、addEditor(emailAddress)やaddEditor(user)としますが、"user"というのは[Userクラス](https://developers.google.com/apps-script/reference/base/user)なのでご注意ください。
なお、スプレッドシートでgetEditors()とすれば配列でUser型のデータが取得できたりします。

```js
function myFunction(){
  //前述のフォルダオブジェクトdestfolderをそのまま使用します

  //保存先フォルダdestfolderに閲覧者"hoge.taro"さんの名前をくっつけた子フォルダを作る
  var foldername = destfolder.getName() + "_" + "hoge.taro";
  var newfolder = destfolder.createFolder(foldername);

  //emailで権限をつける
  var email = "hoge.taro@******"
  newfolder.addViewer(email);
}
```

### フォルダへのファイル追加・削除
Google Driveの場合、普通のファイルサーバーにおけるファイル管理とはちょっと性質が異なりますよね。「フォルダにファイルを置く」というよりかは、「ファイルにフォルダ（という名のタグ）をつける」イメージといえばよいでしょうか。

しかし、それは画面上で操作する際のイメージであって、Apps Scriptでは、保存したいフォルダオブジェクトに対してファイルをaddFile(sheet)することになります。

```js
function myFunction(){
  //前述のフォルダオブジェクトdestfolderをそのまま使用します
  //前述のフォルダオブジェクトnewfolderをそのまま使用します

  //"hoge.taro"さんの子フォルダに追加したいスプレッドシート
  var sheet = DriveApp.getFileById("**************"); 

  //フォルダへのファイル追加
  newfolder.addFile(sheet);
  //ファイル削除
  newfolder.removeFile(sheet);
}
```

### ファイルへの権限追加・削除
フォルダへの権限の追加、削除と同じです。
addEditor(emailAddress)やremoveEditor(emailAddress)とすればよいです。

ただし、注意点が二つあります。

1. マウスでファイルを移動させると「今のフォルダから見えなくなりますよー」というダイアログがでますが、Apps Scriptで組んでる時にはそういうのはありません。
つまり、 __addするのはいいけど、元のフォルダから消す必要があるのならきちんとremoveもしておく必要がある__ ということです。

2. マウスでフォルダにファイルを追加した場合、フォルダの権限が自動的に適用されますよね。
しかし、Apps Scriptではそんなの勝手にはやってくれません。
 __folder.addFile(sheet)とは別に、明示的にaddEditor(emailAddress)などとして権限を付与する__ 必要があります。

### まとめ（実際に実装した内容）
対象となるスプレッドシートに設定したい閲覧者は当然複数います。
その情報は配列でもたせましたが、配列をループしながら閲覧者ごとのフォルダが存在するかどうかチェックする必要があるわけです。

そして閲覧者の名前を含む子フォルダが存在するかチェックしたのち、
* フォルダが存在すれば、ファイルに閲覧者の権限を追加、および子フォルダにファイルを追加
* フォルダが存在しなければ、子フォルダを作成して権限を付与し、かつ、ファイルに閲覧者の権限を追加、および子フォルダにファイルを追加
ということにしました。

（正直、他にもっとスマートなやり方がありそうですが、何か思うところがあればぜひ教えてください！）

```js
function myFunction(){
  //前述のフォルダオブジェクトdestfolderをそのまま使用します

  //閲覧権限を付与したい人のリスト（このリストを作る処理は省略します）
  var addlist_email = [];
  var addlist_name = [];

  //子フォルダに追加したいスプレッドシート
  var sheet = DriveApp.getFileById("**************"); 

  //閲覧者ごとに子フォルダを探して処理を実施
  for ( var i in addlist_name ){
  
    var destfolder_childs = destfolder.getFolders();              //このタイミング！で子フォルダのIteratorを取得

    if ( destfolder_childs.hasNext() ){                           //何かしら子フォルダがあるので処理をする

      var flag = 0;                                               //存在チェック用

      while ( destfolder_childs.hasNext() ){
        var folder = destfolder_childs.next();
        if ( folder.getName().indexOf(addlist_name[i]) != -1 ){  //名前が含まれる場合
          flag = 1;                                              //"1"：該当するフォルダが存在した
          sheet.addViewer(addlist_email[i]);                     //ファイルに権限追加
          folder.addFile(sheet);                                 //子フォルダにファイル追加
        }
      }

      //子フォルダが存在しなかった場合
      if (flag == 0){
        var foldername = destfolder.getName() + "_" + addlist_name[i];
        var newfolder = destfolder.createFolder(foldername);               //任意の名称でフォルダ作成
        newfolder.addViewer(addlist_email[i]);                             //そのフォルダに閲覧者を設定
        newfolder.addFile(sheet);                                          //ファイルを追加
        sheet.addViewer(addlist_email[i]);                                 //ファイルに権限追加
      }
    }else{
      //初回はまったく子フォルダが存在しないのでその時の処理も書いた
    }
  }  
}
```
>子フォルダのFolderIteratorを変数に取得するタイミングは注意しましょう。
Iteratorなのでnext()で最後まで進んだ場合は、hasNext()がfalseになるので、子フォルダを取得するのは都度やりましょう、ということで。


長文になりましたが以上です。


# 積み残しの内容
以下の内容は、最後のApps Scriptに書きたいと思います。どちらかというとJavaScriptの話です。
（そして、未定です。。ずいぶん先になるかも）

* 権限の追加や削除の対象者を（つまり処理すべき差分を）どうやって抽出したかとか
* その他、多次元配列や連想配列を使った時の処理など