web-dev-qa-db-ja.com

Apps Scriptを使用してドライブ内の既存のファイルをドライブにアップロードする方法

はじめに

まず、私がやろうとしていることの目標を紹介しましょう。

  • 以前にファイルを2つの部分に分割していた

  • これらのファイルの両方のサイズを合わせて、50 MBを超える可能性があります(長期的な目標として)。 UrlFetchApp.fetch()にはリクエストのサイズに関する制限があるため、個別にアップロードします。各ファイルは50 MB未満であり、結果としてそれらをマージします。今のところ(Drive APIを試すため)、小さなファイルを使用しています。

  • 最初のファイルは _640000 bytes_(_256_の倍数) _524288 bytes_。私は以前に間違いを犯したことに気づきました。つまり、ファイルのサイズを256の倍数として使用していましたが、_256*1024_の倍数である必要があります

  • 2番目のファイルは _47626 bytes_ _163339 bytes_。

  • curlを使用してファイルを分割し、ドライブにアップロードしました(通常のWebアップロード)。

  • _partial files_から_Resumable Upload_を使用して、_Google Drive API_を使用して_Google Apps Script_を1つずつGoogleドライブにアップロードし、それらが1つのファイルにマージされるようにする予定です。

これまでに試したことはありますか?

  • 昨日、私は 質問 をここに尋ねました。 _resumable upload_を使用して_Drive.Files.insert_を実行しようとしましたが、ユーザーは、以下に引用されている_Drive.Files.insert_を使用してそれを実行することはできないと指摘しました。

残念ながら、現在の段階では、再開可能なアップロードはDrive.Files.insertを使用して実行できません。これはGoogle側の現在の仕様のようです

  • 私が今試みているのは_Google Drive API_を使用することです。以下にコードが含まれています。
_function myFunction() {
    var token = ScriptApp.getOAuthToken();

    var f1_id = '1HkBDHV1oXXXXXXXXXXXXXXXXXXXXXXXX';
    var f2_id = '1twuaKTCFTXXXXXXXXXXXXXXXXXXXX';
    
    var putUrl = 'https://www.googleapis.com/drive/v3/files?uploadType=resumable';
  
    var fileData = {
        name : 'Merged-file-from-GAS',
        file : DriveApp.getFileById(f1_id).getBlob()
    }
    
    var options = {
      method : 'put',
      contentType:"application/json",
      headers : {
        Authorization: 'Bearer ' + token,
        'X-Upload-Content-Type' : 'application/octet-stream',
        'Content-Type' : 'application/json; charset=UTF-8'
      },
      muteHttpExceptions: true,
      payload : fileData
    };
  
    var response = UrlFetchApp.fetch(putUrl, options);
    Logger.log(response.getResponseCode());
    Logger.log(response.getAllHeaders()); 
}

_
  • また、メソッドをpatchに変更してみました

  • headers内に_Content-Length : 640000_を追加しました。その場合、以下のようにエラーが表示されます。

例外:無効な値が指定された属性:Header:Content-Length

  • 空白のresourceを使用してDrive.Files.insert(resource)でファイルを作成しようとしました。次に、変数_var patchUrl = 'https://www.googleapis.com/upload/drive/v3/files/' + fileId + '?uploadType=resumable';_を保持しながら、UrlFetchApp(patchUrl,options)を使用して更新しようとしました

結果

  • ファイルは作成されません。
  • 添付されたコード(初期コード)の結果のロガーログを以下に示します。

[20-05-12 21:05:37:726 IST] 404.0

[20-05-12 21:05:37:736 IST] {X-Frame-Options = SAMEORIGIN、Content-Security-Policy = frame-ancestors 'self'、Transfer-Encoding = chunked、alt-svc = h3-27 = ":443"; ma = 2592000、h3-25 = ":443"; ma = 2592000、h3-Q050 = ":443"; ma = 2592000、h3-Q049 = ":443"; ma = 2592000、h3-Q048 = ":443"; ma = 2592000、h3-Q046 = ":443"; ma = 2592000、h3-Q043 = ":443"; ma = 2592000、quic = ":443"; ma = 2592000; v = "46,43"、X-Content-Type-Options = nosniff、Date = Tue、12 May 2020 15:35:37 GMT、Expires = Mon、01 Jan 1990 00:00:00 GMT、X-XSS-保護= 1; mode = block、Content-Encoding = gzip、Pragma = no-cache、Cache-Control = no-cache、no-store、max-age = 0、must-revalidate、Vary = [Origin、X-Origin]、Server = GSE、Content-Type = text/html; charset = UTF-8}

質問

  • _initiating a upload_をresumableのままにしながら、Apps ScriptからDrive APIを使用してドライブからドライブにファイルを_upload type_する適切な方法は何ですか?

  • 後続のリクエストはどのようにすべきですか?そのため、50 MBを超えるファイルは、その後、マージされたファイルにアップロードできますか?

編集1

修正されたファイルチャンクサイズを使用してもう一度試してみました。同じ問題が続く。

編集2

回答のコードを理解するために、Tanaikeのコードの_// 2_のコードだけを使用して、Locationが取得される方法を理解しました。

_function understanding() {
  var token = ScriptApp.getOAuthToken();
  const filename = 'understanding.pdf';
  const mimeType = MimeType.PDF;

  const url = 'https://www.googleapis.com/drive/v3/files?uploadType=resumable';
  
  const res1 = UrlFetchApp.fetch(url, {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify({name: filename, mimeType: mimeType}),
    headers: {authorization: "Bearer " + ScriptApp.getOAuthToken()
  }});
  const location = res1.getHeaders().Location;
  Logger.log(location);
}
_

これにより、サイズ_understanding.pdf_のファイル_0 bytes_が作成されます。ただし、Logger.log(location)nullをログに記録します。

なぜそうなのですか?

間違いは終点にありました。 _https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable_に設定すると機能します。場所を取得します。

3
Tyler Rake

質問と回答から、以下のような状況と目標を理解できました。

  • サンプルファイルでは、「ファイルA」と「ファイルB」はそれぞれ524,288バイトと163,339バイトです。
  • テストしたスクリプトは、質問に表示されるスクリプトです。
  • 再開可能なアップロードを使用してファイルをマージします。
  • マージされたファイルのmimeTypeはPDFです。

このため、この答えはどうですか?

変更点:

  • 残念ながら、再開可能なアップロードを実現するためのスクリプトは不完全です。 Google Drive APIでの再開可能なアップロードの流れは以下の通りです。 参照

    1. データをアップロードするためのエンドポイントとして使用される場所を取得するためのリクエスト。
      • あなたの場合、新しいファイルが作成されます。したがって、POSTメソッドを使用する必要があります。
    2. データを含めることにより、取得した場所へのリクエスト(あなたの場合、それは各ファイルです。)
      • この場合、ループを使用してデータをアップロードする必要があります。そして、PUTメソッドが使用されます。
      • ここでは、各ファイルのサイズが最も重要です。最後のファイル以外のファイルサイズが262,144バイトの倍数でない場合、エラーにより再開可能なアップロードを実行できません。気をつけてください。

以上の流れで、サンプルスクリプトを用意すると以下のようになります。

使用法:

1. Drive APIを有効にします。

この場合、ドライブAPIが使用されます。高度なGoogleサービスでDrive APIを有効にしてください。これにより、APIコンソールでDrive APIが自動的に有効になります。

サンプルスクリプトの流れは以下の通りです。

  1. 再開可能なアップロードで使用するオブジェクトを作成します。
  2. 再開可能なアップロードを開始するための「場所」を取得します。
  3. 各ファイルをアップロードしてマージします。

2.サンプルスクリプト。

次のスクリプトをコピーして貼り付けてください。そして、ファイルIDを設定してください。この場合、マージするために設定してください。これにご注意ください。

function myFunction() {
  const fileIds = ["###", "###"];  // Please set the file IDs of the file "A" and "B" in order.
  const filename = "sample.pdf";
  const mimeType = MimeType.PDF;

  // 1. Create an object for using at the resumable upload.
  const unitSize = 262144;
  const fileObj = fileIds.reduce((o, id, i, a) => {
    const file = DriveApp.getFileById(id);
    const size = file.getSize();
    if (i != a.length - 1 && (size % unitSize != 0 || size > 52428800)) {
      throw new Error("Size of each file is required to be the multiples of 262,144 bytes and less than 52,428,800 bytes.");
    }
    o.files.Push({data: file.getBlob().getBytes(), range: `bytes ${o.size}-${o.size + size - 1}\/`, size: size.toString()});
    o.size += size;
    return o;
  }, {size: 0, files: []});

  // 2. Retrieve "location" for starting the resumable upload.
  const url = "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable";
  const res1 = UrlFetchApp.fetch(url, {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify({name: filename, mimeType: mimeType}),
    headers: {authorization: "Bearer " + ScriptApp.getOAuthToken()
  }});
  const location = res1.getHeaders().Location;

  // 3. Upload each file and merge them.
  fileObj.files.forEach((e, i) => {
    const params = {
      method: "put",
      headers: {"Content-Range": e.range + fileObj.size},
      payload: e.data,
      muteHttpExceptions: true,
    };
    const res = UrlFetchApp.fetch(location, params);
    const status = res.getResponseCode();
    if (status != 308 && status != 200) {
      throw new Error(res.getContentText());
    }
    if (status == 200) {
      console.log(res.getContentText())
    }
  });

  // DriveApp.createFile()  // This comment line is used for automatically detecting the scope of "https://www.googleapis.com/auth/drive" by the script editor. So please don't remove this line.
}

結果:

再開可能なアップロードが完了すると、ログに次の結果が表示されます。そして、ルートフォルダでマージされたファイルを見ることができます。

{
 "kind": "drive#file",
 "id": "###",
 "name": "sample.pdf",
 "mimeType": "application/pdf"
}

注意:

  • これは簡単なサンプルスクリプトです。実際の状況に合わせて変更してください。
  • 上記のスクリプトを、「ファイルA」と「ファイルB」が524,288バイトと163,339バイトであるサンプル状況についてテストしました。したがって、このスクリプトを使用してサイズが約50 MBの複数のファイルをマージすると、エラーが発生します。
  • 大きなファイルを使用しているときにメモリエラーが発生した場合、現段階ではこれがGoogle側の仕様のようです。気をつけてください。

参照:

4
Tanaike

田池の答えは完璧ではありません。エレガントで、array.reduce関数について学ぶのにも役立ちました。この質問をする前は、JavaScriptに関する知識はほとんどなく、Google Drive APIの使用に関する知識はほとんどありませんでした。

私の意図は、resumable uploadのプロセス全体を言語としてGoogle Apps Scriptを使用して段階的に学習することでした。 Tanaikeのコードを参照として使用生産性が高く、扱いやすく、洗練されている代わりに、(少なくとも)resumable uploadが段階的にどのように機能するかを理解できるスクリプトを作成しました。私はループもオブジェクトも配列も使用していません。

ステップ1(必要な変数を宣言する)

  var fileId1 = "XXXXXXXXXXX"; //id of the first file
  var fileId2 = "YYYYYYYYYYY"; //id of the second file
  var filename = "merged.pdf"; //name of the final merged file
  var mimeType = MimeType.PDF; //Mime type of the merged file

ステップ2(再開可能なアップロードを開始する)

//declare the end point
const url = "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable";

//Send the request
//Method to be used is Post during initiation
//No file is to be sent during initiation
//The file name and the mime type are sent
const res1 = UrlFetchApp.fetch(url, {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify({name: filename, mimeType: mimeType}),
    headers: {authorization: "Bearer " + ScriptApp.getOAuthToken()
  }});

ステップ3(再開可能なセッションURIを保存)

const location = res1.getHeaders().Location;

ステップ4(a)(ファイル1をアップロード)

注:ステップ4の(a)および(b)は、ループを使用して実行できます。私の場合、ループなしで2回使用しました

  var file = DriveApp.getFileById(fileId1); //get the first file
  var data = file.getBlob().getBytes(); //get its contents in bytes array

//Method used is PUT not POST
//Content-Range will contain the range from starting byte to ending byte, then a slash
//and then file size
//bytes array of file's blob is put in data
  var params = {
    method : "put",
    headers : {
      'Content-Range' : `bytes 0-524287/687627`
    },
    payload : data,
    muteHttpExceptions: true
  }; 

//Request using Resumable session URI, and above params as parameter

  var result = UrlFetchApp.fetch(location,params);

ステップ4(b)(2番目のファイルをアップロード)

//Almost same as Step 4 (a)
//The thing that changes is Content Range
file = DriveApp.getFileById(fileId2);
  data = file.getBlob().getBytes();

  params = {
    method : "put",
    headers : {
      'Content-Range' : `bytes 524288-687626/687627`
    },
    payload : data,
    muteHttpExceptions : true
  };

  result = UrlFetchApp.fetch(location, params);

ステップ4 nの回数を実行する代わりに、ループを使用することをお勧めします。

また、このコードは、プロセス中に発生した可能性のあるエラーをチェックしません。

このコードは、自己学習の実験でしたが、誰かを助けることを願っています。 :)

2
Tyler Rake