2018年4月1日日曜日

Google Apps Script で指定フォルダ以下のファイルを処理する方法(1)

概要
Google Apps ScriptでGoogleドライブの特定のフォルダ以下に含まれるファイルに対して処理を行いたい場合がある。そういった場合、サブフォルダも含め再帰関数を用いたり、配列などを用いて頑張ってループする方法もあるが、6分制限対策で処理を中断した際の再開方法が面倒です。
ということで指定したフォルダ以下のファイルを一つのFileIteratorオブジェクトとして取得する関数を作成した。その関数と使い方を簡単に説明する。

まずはいきなりソースコード
// 指定したフォルダ以下に含まれるすべてのファイルを列挙
function getFiles(folderID)
{
  // 「指定したフォルダ内に配置されている」という検索式を作る。
  var searchFileParams = Utilities.formatString("'%s' in parents", folderID);
  var searchFolderParams = searchFileParams;
  var folders;
  // フォルダ検索結果が空になるまで検索を続ける。

  while( (folders = DriveApp.searchFolders(searchFolderParams)).hasNext() )
  {
    // 最後に先頭の" or "を削除したいので先頭を示す"@"を入れておく
    searchFolderParams = "@";
    while( folders.hasNext() )
    {
      // このループでは検索式に" or 'XXX' in parents"を追加していく 
      var folder = folders.next();
      searchFolderParams += Utilities.formatString(" or '%s' in parents", folder.getId() );
    }
    // でき上がった検索式の先頭から不要な"@ or "を削除する。
    // searchFolderParamsは次の階層のフォルダ検索式
    searchFolderParams = searchFolderParams.replace( "@ or ","" );
    // searchFileParamsはファイルを一気に検索する検索式
    searchFileParams += " or " + searchFolderParams;
  }
  // 指定フォルダ以下全階層のファイルを一気に検索する。
  return DriveApp.searchFiles(searchFileParams);
}

簡単に説明すると、指定されたフォルダを親フォルダに含むファイルを検索するサーチ文を作成し、searchFilesで一気に検索を行う。
デバッグでsearchFileParamsを観察すると動きがわかる。

基本的な使い方
var files = getFiles("205Ps_mAhEFs.......Ugm1m");
while(files.hasNext())
{
  var file = files.next();
  ////////////////////////////////////
  // ファイルごとの処理をここから記載する。
  var logString = file.getName()+":"+file.getId();
  Logger.log(logString);
  // ファイルごとの処理をここまでに記載する。
  ////////////////////////////////////
}


本格的に使用する場合の使い方
フォルダ以下のファイルすべてに処理したいという要望の時は大体ファイル数が膨大なので、実際にはタイムアウトを考慮する必要がありますので、その場合の実装例。
// メニュー>実行>関数を実行>startupFunction を選択して実行してください
function startupFunction(){
  // まずはトリガをすべて削除する。
  var triggers = ScriptApp.getProjectTriggers();
  for (var i = 0; i < triggers.length; i++) {
    ScriptApp.deleteTrigger(triggers[i]);
  }

  // プロパティすべて消す。
  deleteProperties();
  
  // ここにフォルダIDを書く
  var TargetFolderID = ,"205Ps_mAhEFs.......Ugm1m";
  setProperty("TargetFolderID",TargetFolderID);

  // トリガを設定する。(10分間隔)
  trigger = ScriptApp.newTrigger("triggerFunction")
  .timeBased()
  .everyMinutes(10)
  .create();

  triggerFunction();
}

function triggerFunction() {
  var StartTime   = new Date();

  var ContinuationToken = getProperty("ContinuationToken");
  if( ContinuationToken != null )
  {
    var files = DriveApp.continueFileIterator(ContinuationToken);
  }
  else
  {
    var TargetFolderID = getProperty("TargetFolderID");
    var files = getFiles(TargetFolderID);
  }

  while(files.hasNext()) {
    var CurrentTime = new Date();
    // トリガを設定する。(5分間実行していたら止める。)
    if( (CurrentTime - StartTime) > 5*60*1000 ){
      setProperty("ContinuationToken",files.getContinuationToken());
      return;
    }
    var file = files.next();
    ////////////////////////////////////
    // ファイルごとの処理をここから記載する。
    var logString = file.getName()+":"+file.getId();
    Logger.log(logString);
    // ファイルごとの処理をここまでに記載する。
    ////////////////////////////////////
  }
  
  deleteProperties();
  var triggers = ScriptApp.getProjectTriggers();
  for (var i = 0; i < triggers.length; i++) {
    ScriptApp.deleteTrigger(triggers[i]);
  }
}

なおここで使用しているProperty関連の関数については「Google Apps Scriptで設定ファイルの保存・読込」を参照してほしい。ContinuationTokenが異常に長くなることがあり、PropertiesServiceクラスを用いて保存しようとすると失敗することがある。PropertiesServiceクラスは9217文字以上を指定すると失敗する模様。

関連情報

0 件のコメント:

コメントを投稿

質問、要望、指摘など書いていただいてもよいですが、対応できるとは限りませんのでご了承ください。私に対するものも含め他の人を嫌な気分になるようなコメントは避けてください。