Google Apps Scriptでは、Google Driveのファイルを検索し表示することが出来るのですが、どういうわけか、DocsListとDriveAppの2種類が存在しています。ネットを散策しているとどうも、DriveAppのほうが使いやすいし高機能ということで、こちらを使って、ドライブ内のファイルを検索し、値を取得し、Spreadsheetに記述するまでをやってみたいと思います。結構、検索といっても癖があったり、ちゃんとドキュメントに記載がなかったりして、とても重要な機能なのに、Googleのリファレンスの不親切さには・・・・
今回はサンプルファイルはナシですが、想定としては以下のようなケースを想定しています。
Google DriveとそのAPIの特徴として、以下の点が挙げられます。
こんな感じです。
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など様々な形のメソッドが用意されているものの、正直、その中でも条件を自由に設定できるsearchFilesとsearchFoldersに絞って取り上げることとする。
これがもっとも面倒な点。そして自分がハマりまくった点でもあります。理解するのにちょいと時間を消費してしまいました。
検索条件は自由気ままに入れることができるわけじゃなく、ルールがあります。下の参考リンクにあるDrive SDK - Search for Filesを見ると、指定方法がわかるのですが、いかにもGoogle Driveだなぁという項目が揃っています。
上記のうちで、普段使いで使いそうなのはそう多くありません。特に取り上げるべきは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のサイトで見つけたものです。これで、基準となる大元の親フォルダを作成して、そのキーをプロジェクトプロパティに格納することができます。