グーグルドライブで、特定のフォルダ以下のフォルダとファイルの一覧を書き出すスクリプト

グーグルドライブにみんなでファイルを登録するのは良いんだけれど、
一体誰が何を入れたのかわからなくなって、サテハテ？となったので作った。

# 大まかな方針（ざっくり）

* スクリプトをメニューから起動できるようにする
* 新シート（今日日付）を作る
* フォルダの一覧を配列に取得する
* フォルダの一覧配列を使い、ファイル一覧を取得し、スプレッドシートに書き出す
    * ファイル名だけじゃなく、登録者、登録日も載る
    * リンクをクリックすればプレビューも

<img width="500" alt="スクリーンショット 2017-06-05 18.47.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/184128/cc160ca7-d2ee-2e41-baa3-64280c291d4c.png">

＜だいたいこんな感じになる＞


# コード

```Javascript
// メニューに表示させる
function onOpen(e){
    var arr = [
        {name: "リスト作成", functionName: "listAllMain"}
    ];
    var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    spreadsheet.addMenu("スクリプト", arr);
}


// メイン処理
function listAllMain() {
  insertNewSheet();
  listAllFandF();
}


// 新シートを挿入、今日日付
function insertNewSheet() {

  // 日付取得
  var td = new Date();
  
  // YYYYMMDD形式に変換（考えた人、賢いなあ）
  var year_str = td.getFullYear();
  var month_str = td.getMonth()+ 1; // monthはなんと0-11
  var day_str = td.getDate();

  month_str = ('0' + month_str).slice(-2);
  day_str = ('0' + day_str).slice(-2);
  
  format_str = 'YYYYMMDD';
  format_str = format_str.replace(/YYYY/g, year_str);
  format_str = format_str.replace(/MM/g, month_str);
  format_str = format_str.replace(/DD/g, day_str);

  // 一番左にスプレッドシートを作成、名称はYYYYMMDD
  var objSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  objSpreadsheet.insertSheet(format_str, 0);
  // あまり綺麗じゃないんだけど、一番左のシートをアクティブにする
  SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].activate();
}


// 開始フォルダの下の全てのフォルダを取得後、全てのファイルを取得
// http://excel-ubara.com/apps_script1/GAS038.html と037を参考にした
function listAllFandF() {

  var sheet = SpreadsheetApp.getActiveSheet();
  var key = "xxxxxxxxxxxxxxxxxxxxxxxxx"; // 探索を開始するフォルダ（固定してる）
  var stt = DriveApp.getFolderById(key);
  var name = "";
  var i = 0; // 配列の行位置を記録している
  var j = 0; // 配列を上から拾って、サブフォルダ探索。探索は配列の二行目から行う
  var folderlist = new Array(); //フォルダリストを格納する配列
  
  sheet.clear() //シートをクリア

  // 一行目を配列に書き出す（開始フォルダ）
  folderlist.push([stt, key]);

  // nameに開始フォルダ追加する
  name = stt + " > ";
  
  // フォルダリストを配列に書き出す
  do {
    //フォルダ一覧を取得
    var folders = DriveApp.searchFolders("'"+key+"' in parents");
    //フォルダ一覧からフォルダを順に取り出し、配列にフォルダ名称とIdを出力
    while(folders.hasNext()){
      i++;
      var folder = folders.next();
      var tmparray = new Array();
      tmparray.push(name + folder.getName());
      tmparray.push(folder.getId());
      folderlist.push(tmparray);
    }

    //配列の上から順にフォルダ名称（>をつける）とIdを取り出す
    j++;
    // j（配列を取りに行こうとする行数）がi（配列の行数、ゼロから始まる）と同じか小さいなら
    if(j <= i){
      name = folderlist[j][0] + " > ";
      key = folderlist[j][1];
    }
  } while (j <= i); //配列を最後まで舐める

  // folderlist配列（フォルダリスト）を使って、フォルダ下にあるファイルを取得しシートに吐き出し
  j = 1; //シートへの出力行
  // ヘッダ記入：直書きで汚くて申し訳ない
  sheet.getRange(j, 1).setValue("フォルダ");
  sheet.getRange(j, 2).setValue("ファイル");
  sheet.getRange(j, 3).setValue("作成日");
  sheet.getRange(j, 4).setValue("オーナー");
  sheet.getRange(j, 5).setValue("URL");
  j++;
  // ボディ記入
  for (i=0; i<=folderlist.length-1; i++) {
    var key = DriveApp.getFolderById(folderlist[i][1]).getId();
    var files = DriveApp.searchFiles("'"+key+"' in parents");
    while(files.hasNext()){
      var file = files.next();
      sheet.getRange(j, 1).setValue(folderlist[i][0]);
      sheet.getRange(j, 2).setValue(file.getName());
      sheet.getRange(j, 3).setValue(file.getDateCreated());
      sheet.getRange(j, 4).setValue(file.getOwner().getName());
      sheet.getRange(j, 5).setValue(file.getUrl());
      j++;
    }
  } 
  
}

```

# 解説。ってコメントにほとんど書いちゃったけど

## メニューからスクリプト実行できるようにする

```Javascript
// メニューに表示させる
function onOpen(e){
    var arr = [
        {name: "リスト作成", functionName: "listAllMain"}
    ];
    var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    spreadsheet.addMenu("スクリプト", arr);
}
```

なんかまあ、おまじない。
listAllMain関数を呼び出させている。

## スクリプトを実行したら、まずは（今日日付の）新シートを作る

```Javascript
// 新シートを挿入、今日日付
function insertNewSheet() {
  // 日付取得
  var td = new Date();
  
  // YYYYMMDD形式に変換（考えた人、賢いなあ）
  var year_str = td.getFullYear();
  var month_str = td.getMonth()+ 1; // monthはなんと0-11
  var day_str = td.getDate();

  month_str = ('0' + month_str).slice(-2);
  day_str = ('0' + day_str).slice(-2);
  
  format_str = 'YYYYMMDD';
  format_str = format_str.replace(/YYYY/g, year_str);
  format_str = format_str.replace(/MM/g, month_str);
  format_str = format_str.replace(/DD/g, day_str);
```

tdに今日の日付を取得して、そこから年月日の文字列を取得。
最後に気づいてギャボーてなったのは、月が0-11であること。
JavaScript、すげ。

月と日を2桁の文字列で取得して、YYYMMDDという文字列に対し置換。
この処理はどこかで見つけたのだけれど、賢いなあ。

```Javascript
  // 一番左にスプレッドシートを作成、名称はYYYYMMDD
  var objSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  objSpreadsheet.insertSheet(format_str, 0);
  // あまり綺麗じゃないんだけど、一番左のシートをアクティブにする
  SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].activate();
}
```

そしてスプレッドシートを作成。
スプレッドシート名称を指定してアクティブにしたかったけど、わからなくて。
不本意ながら、一番左のシートをアクティブにするという邪道な感じ。

## そしてフォルダ一覧を配列に取得

大まかには

* フォルダ一覧配列（folderlist）→フォルダ一覧（folders）→フォルダ1つ（folder）


```Javascript
  var sheet = SpreadsheetApp.getActiveSheet();
  var key = "xxxxxxxxxxxxxxxxxxxxxxxxx"; // 探索を開始するフォルダ（固定してる）
  var stt = DriveApp.getFolderById(key);
  var name = "";
  var i = 0; // 配列の行位置を記録している
  var j = 0; // 配列を上から拾って、サブフォルダ探索。探索は配列の二行目から行う
  var folderlist = new Array(); //フォルダリストを格納する配列
```

keyには、探索を開始するフォルダのIdを指定。
汚いんだけれど、固定で入力。
iはfolderlistに何行入っているか（幾つのフォルダを見つけたか）が記録される。
iという変数にするべきではない気がする（けど作ってしまった）。
jはfolderlistから1行ずつ行を拾う時のカウンタになってる。

```Javascript
  // 一行目を配列に書き出す（開始フォルダ）
  folderlist.push([stt, key]);

  // nameに開始フォルダ追加する
  name = stt + " > ";
```

配列の1行目を書く。

```Javascript
  // フォルダリストを配列に書き出す
  do {
    //フォルダ一覧を取得
    var folders = DriveApp.searchFolders("'"+key+"' in parents");
    //フォルダ一覧からフォルダを順に取り出し、配列にフォルダ名称とIdを出力
    while(folders.hasNext()){
      i++;
      var folder = folders.next();
      var tmparray = new Array();
      tmparray.push(name + folder.getName());
      tmparray.push(folder.getId());
      folderlist.push(tmparray);
    }

    //配列の上から順にフォルダ名称（>をつける）とIdを取り出す
    j++;
    // j（配列を取りに行こうとする行数）がi（配列の行数、ゼロから始まる）と同じか小さいなら
    if(j <= i){
      name = folderlist[j][0] + " > ";
      key = folderlist[j][1];
    }
  } while (j <= i); //配列を最後まで舐める
```

ここが（オリジナルのソースをなかなか読み解けなくて）悩んだ。要は

* 探索開始フォルダの下にあるフォルダを、配列に入れる
* 配列を上から1行取り（1行目はスキップ）、その配列が指しているフォルダの下にあるフォルダを、配列に入れる
    * これがややこしい……誰か図で書いてー、ですね
* 配列の次の1行を取り、その配列が指しているフォルダの下にあるフォルダを、配列に入れる
* ということを、配列の終わりまでやる
    * `j++;` のタイミングで`i > j`になってしまい、エラー（そんな行はない！と言われる）のでif文で回避

## 後は配列を1行ずつ処理して、ファイルリストを吐き出す

```Javascript
  // folderlist配列（フォルダリスト）を使って、フォルダ下にあるファイルを取得しシートに吐き出し
  j = 1; //シートへの出力行
  // ヘッダ記入：直書きで汚くて申し訳ない
  sheet.getRange(j, 1).setValue("フォルダ");
  sheet.getRange(j, 2).setValue("ファイル");
  sheet.getRange(j, 3).setValue("作成日");
  sheet.getRange(j, 4).setValue("オーナー");
  sheet.getRange(j, 5).setValue("URL");
  j++;
```

ヘッダ行をスプレッドシートに書く。直打ちで汚くて申し訳ない。

```Javascript
  // ボディ記入
  for (i=0; i<=folderlist.length-1; i++) {
    var key = DriveApp.getFolderById(folderlist[i][1]).getId();
    var files = DriveApp.searchFiles("'"+key+"' in parents");
    while(files.hasNext()){
      var file = files.next();
      sheet.getRange(j, 1).setValue(folderlist[i][0]);
      sheet.getRange(j, 2).setValue(file.getName());
      sheet.getRange(j, 3).setValue(file.getDateCreated());
      sheet.getRange(j, 4).setValue(file.getOwner().getName());
      sheet.getRange(j, 5).setValue(file.getUrl());
      j++;
    }
  } 
```

* folderlistから1行取り出し、
* そのフォルダの下にあるファイルを全部取得し、
* スプレッドシートに書く。

の繰り返し。

## スピード

だいたい500ファイル弱で、5分ぐらいかかっているような気がする（気分）。


# 参考にしたサイト

http://excel-ubara.com/apps_script1/GAS037.html
http://excel-ubara.com/apps_script1/GAS038.html

最初、GAS038にコードが載っていて、こりゃあ良いやと思って使ってみたところ、
思った通りに動かずスタック。
GAS037に立ち戻って、コードをあれこれ書き直して動くようにしてみた。
パズルー。