ファイルフォルダの探索

Google Apps Scriptでは、Google Driveのファイルを検索し表示することが出来るのですが、どういうわけか、DocsListとDriveAppの2種類が存在しています。ネットを散策しているとどうも、DriveAppのほうが使いやすいし高機能ということで、こちらを使って、ドライブ内のファイルを検索し、値を取得し、Spreadsheetに記述するまでをやってみたいと思います。結構、検索といっても癖があったり、ちゃんとドキュメントに記載がなかったりして、とても重要な機能なのに、Googleのリファレンスの不親切さには・・・・

今回はサンプルファイルはナシですが、想定としては以下のようなケースを想定しています。

  1. 特定のフォルダにスプレッドシートデータがたくさん入っている。
  2. さらにその中にはサブフォルダがあり、その中にもスプレッドシートデータが入っている。
  3. 親フォルダも子フォルダもスプレッドシートデータ以外にも書類が混じっている。
  4. この中から、サブフォルダ内まで含めて、スプレッドシートのみをリストアップしたい。
  5. リストアップしたデータは、現在のスプレッドシートのシートに、ファイル名とIDを書き込みしたい。

特徴

Google DriveとそのAPIの特徴として、以下の点が挙げられます。

  1. ディレクトリという概念があるわけじゃない。あくまでも、人間がわかりやすい形で、ディレクトリツリー構造のようなインターフェースになっているだけで、すべてのファイルがルートディレクトリに存在している状態を思い浮かべると良い。
  2. 特定のディレクトリに属しているという情報をファイルが持っている。
  3. keyで管理されているため、同じファイル名の同じファイルが同じレベルで存在することが出来る。
  4. 検索をする際に、サブディレクトリまで精査してくれない。
  5. 細かく検索条件を設定できるものの、ちょっといまいちな点も多い。

こんな感じです。

WindowsやOSXのようなOS上でのファイルの検索とは少々異なる点があるため、正直な所、決して使いやすいわけではない。しかし、複数のファイルのkeyを知る必要性があるようなケースでは、必須のテクニックであり、そこで得たkeyのデータを元に、データを取りに行くなどのコードを書くことになるわけです。

ソースコード

function driveman(){
  //Drive APIで特定のファイルをリストとして取得
  var key = ScriptProperties.getProperty('key');
  var files = DriveApp.searchFiles("mimeType = 'application/vnd.google-apps.spreadsheet' and '"+key+"' in parents"); 
  
  //filelistシートに移動し内容をクリアする
  var mvsheet = SpreadsheetApp.getActiveSpreadsheet();
  mvsheet.getSheetByName("filelist").activate();
  var sheet = SpreadsheetApp.getActiveSheet();
  sheet.clear();  
  
  //まずカレントディレクトリ内のファイルリストの書き込み
  var Cell = SpreadsheetApp.getActiveSheet().getRange("A1");//offsetの参照点セルを設定
  Cell.activate();
  var i = 0;
  while(files.hasNext()){
    var file = files.next();
    Cell.offset(i, 0).setValue(file.getName());
    Cell.offset(i, 1).setValue(file.getId());
    i++;
  }
  
  //次にフォルダがあるか検索し、あった場合にはフォルダリスト配列に格納する
  var folderlist = new Array();
  var folders = DriveApp.searchFolders("'・・・所属してる親フォルダのkeyをここにいれる・・・' in parents");
  
  var j = 0;
  while (folders.hasNext()) {
   var folder = folders.next();
   folderlist[j] = folder.getId();
   j++;
  }
  
  //フォルダリストに基づき、各フォルダキーの中のファイルを書き出していく  
  for(var xx = 0;xx<folderlist.length;xx++){
     var curFolder = folderlist[xx];
     files = DriveApp.searchFiles("mimeType = 'application/vnd.google-apps.spreadsheet' and '"+curFolder+"' in parents"); 
     while(files.hasNext()){
       var file = files.next();
       Cell.offset(i, 0).setValue(file.getName());
       Cell.offset(i, 1).setValue(file.getId());
       i++;
     }
  }
}

※今回は、プロジェクトプロパティに検索対象のフォルダのIDを予め、入れておいてあるので、冒頭のScriptProperties.getProperty('key');で、keyという名前のキーの中にある格納済みのIDを取得しています。

ポイント

詳しくは、DriveAppのリファレンスを見ていただければ良いのだが、本来、このAPIは、まさにエクスプローラ的なものであり、ファイルの検索だけじゃなく作成や削除、ストレージの使用量、追加などを行うことのできるもので、ファイル操作をする上では欠かせないものなのですが、検索に限って言えば、ちょっとリファレンスに乗っている内容だけでは理解に至らなかったので、ネットにある情報を探索し、ここにまとめておく。また、検索に関しても、getFilesやらgetFileByIdなど様々な形のメソッドが用意されているものの、正直、その中でも条件を自由に設定できるsearchFilessearchFoldersに絞って取り上げることとする。

検索条件概要

これがもっとも面倒な点。そして自分がハマりまくった点でもあります。理解するのにちょいと時間を消費してしまいました。

検索条件は自由気ままに入れることができるわけじゃなく、ルールがあります。下の参考リンクにあるDrive SDK - Search for Filesを見ると、指定方法がわかるのですが、いかにもGoogle Driveだなぁという項目が揃っています。

  1. title ・・・要するにファイル名での検索をする
  2. fullText・・・要するに全文検索。そのワードが入っているファイルを検索する。
  3. mimeType・・・ファイルの形式。正直ちょっと指定には癖がある。今回のキモの一つ。
  4. modifiedDate・・・編集日で検索をする。
  5. lastViewedByMeDate・・・最後に自分が見たファイルの日付で検索するもの。
  6. trashed・・・削除したファイルを検索。ゴミ箱あさり。
  7. starrad・・・★をつけたファイルを検索。
  8. parents・・・親フォルダIDに所属しているファイルを検索
  9. owners・・・ファイルのオーナーで検索する
  10. writers・・・そのファイルの編集者で検索する
  11. readers・・・そのファイルの閲覧者で検索する
  12. sharedWithMe・・・自分と共有しているファイルについて検索する

上記のうちで、普段使いで使いそうなのはそう多くありません。特に取り上げるべきはmimeTypeの指定方法と、そして、ここにはありませんが、スクリプト中の変数をメソッドの条件式の一部として使う方法が重要になります。検索条件を指定する時の注意点は

  1. かならずダブルコーテーション(" ")でくくった中に条件式を書くこと。
  2. 条件の指定はシングルコーテーション(' ')でくくった中に書くこと。
  3. 変数を用いる場合にはちょっと特別な書き方をする。下の方にある検索条件の一部に変数を使用するを参考にしてください。

mimeTypeの指定

今回想定しているケースは特定のフォルダ内のファイルを検索することにあるのですが、スプレッドシートデータだけをヒットさせたい場合には、ファイルタイプが絶対不可欠です。下のEnum MimeTypeで紹介されているようなものでも動くのかもしれませんが、今回は普通通り、きちんとしたMimeTypeを指定することにしました。この辺りがしっかりリファレンスに記載されていなかったりします。Google Docs関係のMimeTypeはこちらにまとめられていますので参考にしましょう。

今回は、スプレッドシートのみをヒットさせたいので、以下のように記述します。

DriveApp.searchFiles("mimeType = 'application/vnd.google-apps.spreadsheet'");

この条件式で、スプレッドシートのみをリストアップすることが出来るようになります。

検索条件の一部に変数を使用する

プログラミングじゃ当たり前のように変数を検索条件の一部に使用するわけなのですが、VBA上がりの人間などだと、ダブルコーテーションで閉じたあとに、アンパサンドや+などで続ければ条件式に入ると思いがち。しかし、Google Apps Scriptでは、シングルコーテーションでくくり、なおかつダブルコーテーションでくくり、その中で+変数+と記述するのがルールになっています。これをやらないとエラーになります。気がつくのに自分は無駄な時間を大量に消費しました。ということで、以下のようになります。keyという変数の中の値を検索条件の一部として使用しています。

DriveApp.searchFiles("'"+key+"' in parents");

複数の条件式をつなげる場合には、andなどをいれる点は、他の言語と変わりありません。

基準フォルダから下のフォルダ階層をすべてリストアップするコード

現在、Google Driveはフォルダ単位でのコピーができません。よって、Google Apps Scriptで作成し作りこんだファイルの複製は、サブフォルダまで含めて、すべて自分で手動で行う必要性があります。ものすごい無駄な作業で時間を消費するので、非常に迷惑な仕様です。とはいえ、これを簡単に自作スクリプトで実現するのは難しいので、とりあえず、基準となるフォルダから下のフォルダ階層を解析し、リストアップするスクリプトを作成しました。

function driveman(){
  //Drive APIで特定のファイルをリストとして取得
  var key = ScriptProperties.getProperty('key');
  var nowdir = key;
  var totalcount=0;
  
  //folderlistシートに移動し内容をクリアする
  var mvsheet = SpreadsheetApp.getActiveSpreadsheet();
  mvsheet.getSheetByName("folderlist").activate();
  var sheet = SpreadsheetApp.getActiveSheet();
  sheet.clear(); 
  var Cell = SpreadsheetApp.getActiveSheet().getRange("A1");//offsetの参照点セルを設定
  Cell.activate();
  
  //フラグ類
  var subfolderflag = 0;  //1なら有り、0なら無し。
  
  //次にフォルダがあるか検索し、あった場合にはフォルダリスト配列に格納する
  var folderlist = new Array();
  var folders = DriveApp.searchFolders("'"+key+"' in parents");
  
  if(folders.length == 0){
  
    //普通に指定フォルダ内のファイルのリストを書き出して終了
  }else{
    //サブフォルダフラグを立てる
    subfolderflag=1;
    //folderlistシートへ書き込み
    while(folders.hasNext()){
      var folder = folders.next();
      Cell.offset(totalcount, 0).setValue(folder.getName());
      Cell.offset(totalcount, 1).setValue(folder.getId());
      Cell.offset(totalcount, 2).setValue(nowdir);
      folderlist.push(folder.getId());
      totalcount++;
    }
   }
  
//サブフォルダ以降のフォルダの探索ルーチン
  var sub = 1;
  while (sub == 1) {
    var nowponyo = folderlist.length;
    

for(var i = 0;i<nowponyo;i++){

            //0番目の配列のkeyを取得する

key = folderlist[0];

            
            //そのkeyのフォルダにぶら下がるフォルダをリストアップ
            folders = DriveApp.searchFolders("'"+key+"' in parents");
            nowdir = key;
            
            //folderlistシートへ書き込み
            if(folders.hasNext()==true){
              while(folders.hasNext()){
                 var folder = folders.next();
                 Cell.offset(totalcount, 0).setValue(folder.getName());
                 Cell.offset(totalcount, 1).setValue(folder.getId());
                 Cell.offset(totalcount, 2).setValue(nowdir);
                 folderlist.push(folder.getId());
                 totalcount++;
              }
            }
          
            //配列0番目を削除と詰める

folderlist.splice(0,1);

}

      //folderlistが空ならsubfolderflagを0にする

if(folderlist.length == 0){

sub=0;

}

  }
    
}

1枚のシート(シート名はfolderlist)というものが入っているだけです。今後、このリストを元に、基準フォルダ内のファイルとフォルダのコピーを行うルーチンを追加予定ですが、道のりは険しい・・・

folderlistに出来上がるリストは、左から【フォルダ名】【そのフォルダのID】【所属する親フォルダのID】となっています。現在は、解析してリストアップまでしかできませんが、次に、この表を元にフォルダ構造を作成するルーチンを考え中です。その際にはこのfolderlistの4つ目のカラムに新規フォルダIDと新規フォルダIDが所属する新規親フォルダのIDが加わる予定です。その次くらいに、ファイルのコピー作成ルーチンですね。

ちなみに、新しいフォルダを作成し、その作ったばかりのフォルダのkeyを取得するコードを書いてみました。とはいえ、Google Driveは同じファイル名でもドライブ内に存在ができてしまうために、作ったばかりとはいえ、そのファイルがユニークであることを特定するのが難しいので、GUIDを生成する関数を別に用意し、フォルダ名にNewFolderとGUIDを付与することにしました。そして、おいおい、新規フォルダの名前を別にinputboxからもらって、一番最後にフォルダのリネームを行うという仕様を考えています。

function dirmake(){
  //まずは、新しくルートフォルダを作成し、フォルダのキーを取得して、プロジェクトプロパティに格納
  var pinpon = DriveApp.createFolder("NewFolder" + guid());
  var rootfolder = DriveApp.getFoldersByName(pinpon);
  var folder = rootfolder.next();
  var rootkey = folder.getId();
  ScriptProperties.setProperty('make',rootkey);
}
function s4() {
  return Math.floor((1 + Math.random()) * 0x10000)
             .toString(16)
             .substring(1);
};
function guid() {
  return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
         s4() + '-' + s4() + s4() + s4();
}

GUID生成関数はStackOverFlowのサイトで見つけたものです。これで、基準となる大元の親フォルダを作成して、そのキーをプロジェクトプロパティに格納することができます。