GoogleドライブのファイルをGoogle Cloud Storageへコピーする

Google Cloud APIの中には、gs://[bucket]/[path]の形式になっているCloud StorageのURLを与えることができるものがある。今回はGoogleドライブに入っているファイルをCloud Storageにコピーしてgs://のURLとして使えるようにしたかった。

streamをうまく使うことで、一時ファイルを作らずにコピーすることができた。

準備

以下のパッケージをインストールしておく。

$ npm i googleapis @google-cloud/storage

ドライブ用にGoogle APIのOAuthクライアントIDを作成する。APIとサービスの認証情報から[OAuth クライアント ID の作成]で作成できる。コマンドラインツールの場合は[その他]を選んで作成する。JSONをダウンロードしておく。

Cloud Storage用にサービスアカウントキーを発行して、これもJSONをダウンロードしておく。

Googleドライブの認証

ドライブ公式のクイックスタートをまとめ直したクラスを作った。

https://gist.github.com/iseebi/4d21bf68c79bc020ea3515693676a35f

コピー関数

こんな感じの関数を書いた。

import {Bucket} from "@google-cloud/storage";
import {drive_v3} from "googleapis";
import {Readable} from "stream";
import Drive = drive_v3.Drive;

async function copyDriveToStorage(drive: Drive, bucket: Bucket, driveFileId: string, uploadFileName: string) {
  const meta = await drive.files.get({fileId: driveFileId, supportsTeamDrives: true, fields: 'mimeType'});
  const media = await drive.files.get({fileId: driveFileId, supportsTeamDrives: true, alt: 'media'}, {responseType: 'stream'});
  const uploadFile = bucket.file(uploadFileName);

  await new Promise<void>((resolve, reject) => {
    const downloadStream = media.data as Readable;
    const uploadStream = uploadFile.createWriteStream({
      metadata: {
        cacheControl: 'no-cache',
        contentType: meta.data.mimeType,
      }
    });
    downloadStream.pipe(uploadStream);
    downloadStream.on('error', reject);
    uploadStream.on('error', reject);
    uploadStream.on('finish', resolve);
  });
}

ポイントはこのあたり。

  • drive.file.get のparamsにalt: 'media'、optionsにresponseType: 'stream'を指定
  • その戻り値のdataをReadableにキャストする → 読み込み用のストリーム
  • bucket.filecreateWriteStream → 書き込み用のストリーム
  • 読み込み用のストリームから、書き込み用のストリームにpipeする
  • 書き込み用のストリームがfinishしたら完了、どちらかのストリームがエラーを返したらreject

今回実際に使ったものでは、mimeTypeを事前に絞り込んでいたので、metaの取得はせずにハードコーディングした。

呼び出し

こんな感じで使う。

import {Storage} from "@google-cloud/storage";
import {Authenticator} from "./authenticator";
import {google} from "googleapis";

const auth = await authenticator.authenticateAsync();
const drive = google.drive({version: 'v3', auth});
const storage = new Storage({projectID:'00000000000', keyFilename: 'service_account.json'});
const bucket = storage.bucket('my-bucket-name');

const fileID = '......';
const destination = 'filename';

await copyDriveToStorage(drive, bucket, fileID, destination);