Microsoft 365 OneDrive for Business: ファイルアップロード (Microsoft 365 OneDrive for Business: Upload File)
OneDrive の指定フォルダにファイルをアップロードします。
2020-07-02 (C) Questetra, Inc. (MIT License)
Configs
  • C1: OAuth2 設定名 *
  • C2: アップロードするファイルが保存されているファイル型データ項目 *
  • C3: ファイルをアップロードするフォルダの URL (指定がない場合は、ルートフォルダ)
  • C4: ファイル URL を保存する文字型データ項目
Script
// OAuth2 config sample at [OAuth 2.0 Setting]
// - Authorization Endpoint URL: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
// - Token Endpoint URL: https://login.microsoftonline.com/common/oauth2/v2.0/token
// - Scope: https://graph.microsoft.com/Files.ReadWrite.All offline_access
// - Consumer Key: (Get by Microsoft Azure Active Directory)
// - Consumer Secret: (Get by Microsoft Azure Active Directory)

const LIMIT_SIZE = 4194304; //File size border of Microsoft Graph
const PACKET_MAX_SIZE = 10485760; //size of each packet,must be a multiple of 327680(320KiB): 10485760=10MiB
const GRAPH_URI = "https://graph.microsoft.com/v1.0/";

main();
function main(){
  //// == 工程コンフィグの参照 / Config Retrieving ==
  const oauth2 = configs.get("conf_OAuth2");
  const folderUrl = retrieveFolderUrl();
  const urlDataDef = configs.getObject("conf_fileUrl");
  //// == ワークフローデータの参照 / Data Retrieving ==
  const token = httpClient.getOAuth2Token( oauth2 );
  const files = engine.findData( configs.getObject("conf_uploadedFile") );
  if (files === null) {
    setData(urlDataDef,[""]);
    return;
  }

  //// == 演算 / Calculating ==

  fileCheck(files, urlDataDef, folderUrl);
  const folderInfo = getFolderInfoByUrl( folderUrl, token );
  let uploadedFileUrl = [];

  for(let i = 0; i< files.size(); i++){
    engine.log(files.get(i).getName());
    if(files.get(i).getLength() > LIMIT_SIZE){
      //over 4MB
      processLargeFile(token,files.get(i),folderInfo,uploadedFileUrl);
    }else{
      //under 4MB
      upload(token,files.get(i),folderInfo,uploadedFileUrl);
    }
  }
  //// == ワークフローデータへの代入 / Data Updating ==
  setData(urlDataDef,uploadedFileUrl);
}

/**
  * configからフォルダURLの値を読み出す
  * @return {String} configの値
  */
function retrieveFolderUrl() {
  const folderUrlDef = configs.getObject( "conf_folderUrl" );
  let folderUrl = configs.get( "conf_folderUrl" );
  if ( folderUrlDef !== null ) {
    folderUrl = engine.findData( folderUrlDef );
  }
  return folderUrl;
}

/**
  * アップロードしようとするファイルの数・サイズが適切かどうかチェックする
  * ファイル数、通信制限をチェック
  * 通信数=4MB以下のファイルの数 + <4MBを超えるファイルそれぞれについて>(ceil(ファイルサイズ/パケットの最大サイズ) + 1)
  * @param {Array<File>} files  アップロードしようとするファイル
  * @param {ProcessDataDefinitionView} urlDataDef  URL を保存するデータ項目の ProcessDataDefinitionView
  * @param {String} folderUrl  アップロード先フォルダの URL
  */
function fileCheck(files,urlDataDef,folderUrl){
  const fileNum = files.size(); //number of files
  fileNumCheck(urlDataDef,fileNum);

  let requestNum = 0;
  for (let i = 0; i < fileNum; i++){
    let size = files.get(i).getLength();
    if(size > LIMIT_SIZE){
      requestNum += (Math.ceil(size / PACKET_MAX_SIZE) + 1);
    }else{
      requestNum++;
    }
  }
  if(folderUrl !== "" || folderUrl !== null){
    requestNum++;
  }
  if(requestNum > httpClient.getRequestingLimit()){
    throw "Necessary HTTP requests exceeds the limit.";
  }
}

/**
  * アップロードするデータが複数で URL 出力先のデータ項目が単一行ならエラーにする
  * @param {ProcessDataDefinitionView} dataDef  データ項目の ProcessDataDefinitionView
  * @param {Number} fileNum  アップロードしようとしているファイルの個数
  */
function fileNumCheck(dataDef,fileNum){
  if(dataDef !==  null){
    //Multiple Judge
    if(dataDef.matchDataType("STRING_TEXTFIELD") && fileNum > 1){
      throw "Multiple files are set though the Data Item to save the output is Single-line String."
    }
  }
}

/**
  * フォルダのURLからフォルダ情報(ドライブIDとフォルダID)を取得し、
  * オブジェクトで返す(URLが空の場合はドライブIDをme/drive、フォルダIDをrootにする)
  * @param {String} folderUrl  フォルダのURL
  * @param {String} token  OAuth2 トークン
  * @return {Object} folderInfo  フォルダ情報 {driveId, folderId}
  */
function getFolderInfoByUrl( folderUrl, token ) {
  let folderInfo = {driveId: "me/drive", folderId: "root"};
  if ( folderUrl !== "" && folderUrl !== null ) {
    // 分割代入
    const {
      id,
      parentReference: {
        driveId
      }
    } = getObjBySharingUrl( folderUrl, token );
    folderInfo = {driveId: `drives/${driveId}`, folderId: id};
  }
  return folderInfo;
}

/**
  * OneDriveのドライブアイテム(ファイル、フォルダ)のメタデータを取得し、JSONオブジェクトを返す
  * APIの仕様:https://docs.microsoft.com/ja-jp/onedrive/developer/rest-api/api/shares_get?view=odsp-graph-online
  * @param {String} sharingUrl  ファイルの共有URL
  * @param {String} token  OAuth2 トークン
  * @return {Object} responseObj  ドライブアイテムのメタデータのJSONオブジェクト
  */
function getObjBySharingUrl( sharingUrl, token ) {
  if (sharingUrl === "" || sharingUrl === null) {
    throw `Sharing URL is empty.`;
  }

  // encoding sharing URL
  const encodedSharingUrl = encodeSharingUrl(sharingUrl);

  // preparing for API Request
  let apiRequest = httpClient.begin(); // HttpRequestWrapper
  // com.questetra.bpms.core.event.scripttask.HttpClientWrapper
  // Request HEADER (OAuth2 Token, HTTP Basic Auth, etc)
  apiRequest = apiRequest.bearer( token );
  // Access to the API (POST, GET, PUT, etc)
  let response = apiRequest.get( `${GRAPH_URI}shares/${encodedSharingUrl}/driveItem` ); // HttpResponseWrapper
  const httpStatus = response.getStatusCode();
  const accessLog = `---GET request--- ${httpStatus}\n${response.getResponseAsString()}\n`;
  engine.log(accessLog);
  if (httpStatus >= 300) {
    const error = `Failed to get drive item. status: ${httpStatus}`;
    throw error;
  }
  const responseObj = JSON.parse( response.getResponseAsString() );
  return responseObj;
}

/**
  * 共有URLをunpadded base64url 形式にエンコードする
  * @param {String} sharingUrl  共有URL
  * @return {String} encodedSharingUrl  エンコードされた共有URL
  */
function encodeSharingUrl( sharingUrl ) {
  let encodedSharingUrl = base64.encodeToUrlSafeString( sharingUrl );
  while ( encodedSharingUrl.slice(-1) === '=' ) {
    encodedSharingUrl = encodedSharingUrl.slice(0,-1);
  }
  encodedSharingUrl = "u!" + encodedSharingUrl;
  return encodedSharingUrl;
}

/**
  * ファイルをアップロードする。一回につき一つのみ。
  * @param {String} token  OAuth2 トークン
  * @param {File} file  アップロードするファイル
  * @param {String,String} driveId,folderId  アップロード先ドライブ、フォルダのID
  * @param {Array<String>} uploadedFileUrl  アップロードしたファイルのURLを格納する配列
  */
function upload(token,file,{
  driveId,
  folderId
},uploadedFileUrl){
  const url = `${GRAPH_URI}${driveId}/items/${folderId}:/${encodeURIComponent(file.getName())}:/content`;
  let response = httpClient.begin()
    .bearer(token)
    .body(file)
    .put(url);
  //when error thrown
  const responseStr = response.getResponseAsString();
  const status = response.getStatusCode();
  const accessLog = `---PUT request--- ${status}\n${responseStr}\n`;
  if (status >= 300) {
    engine.log(accessLog);
    throw `Failed to upload. status: ${status}`;
  }
  outputDataSet(status,responseStr,uploadedFileUrl);
}

/**
  * 4MBを超えるファイルのアップロード処理を行う
  * @param {String} token  OAuth2 のトークン
  * @param {File} file  アップロードするファイル
  * @param {String,String} driveId,folderId  アップロード先ドライブ、フォルダのID
  * @param {Array<String>} uploadedFileUrl  アップロードしたファイルのURLを格納する配列
  */
function processLargeFile(token,file,{
  driveId,
  folderId
},uploadedFileUrl){
  const upUrl = createSession(token,file.getName(),{
    driveId,
    folderId
  });
  let range = 0;
  const fileSize = file.getLength();

  fileRepository.readFile(file, PACKET_MAX_SIZE, function(packet){
    //upload each fragment of file
    range = uploadLarge(upUrl,range,packet,fileSize,uploadedFileUrl);
  });
}

/**
  * アップロード用のセッションを作成する
  * @param {String} token  OAuth2 のトークン
  * @param {String} fileName  アップロードするファイルのファイル名
  * @param {String,String} driveId,folderId  アップロード先ドライブ、フォルダのID
  * @return {String}  アップロード先 URL
  */
function createSession(token,fileName,{
  driveId,
  folderId
}){
  const url = `${GRAPH_URI}${driveId}/items/${folderId}:/${encodeURIComponent(fileName)}:/createUploadSession`;
  const body = {
    "item": {
      "@microsoft.graph.conflictBehavior": "replace",
      "name" : fileName
    }
  };
  let response = httpClient.begin()
    .bearer(token)
    .body(JSON.stringify(body), "application/json; charset=UTF-8")
    .post(url);

  const status = response.getStatusCode();
  const jsonStr = response.getResponseAsString();
  const accessLog = `---POST request--- ${status}\n${jsonStr}\n`;
  engine.log(accessLog);
  if(status >= 300){
    throw `Failed to create upload session. status: ${status}`;
  }
  return JSON.parse(jsonStr).uploadUrl;
}

/**
  * 4MBを超えるファイルについて、各部分のアップロードを実行する
  * @param {String} upUrl  アップロードするファイルのファイル名
  * @param {Number} range  これまでにアップロードしたサイズ
  * @param {ByteArrayWrapper} packet  アップロードするバイナリ
  * @param {Number} fileSize  アップロードするファイルのサイズ
  * @param {Array<String>} uploadedFileUrl  アップロードしたファイルのURLを格納する配列
  * @return {Number}  新しい range
  */
function uploadLarge(upUrl,range,packet,fileSize,uploadedFileUrl){
  const packetSize = packet.getLength();
  const rangetxt = `bytes ${range}-${range + packetSize - 1}/${fileSize}`;
  let sending = httpClient.begin()
    .header("Content-Range", rangetxt )
    .body(packet,"application/octet-stream")
    .put(upUrl);

  const status = sending.getStatusCode();
  const responseStr = sending.getResponseAsString();
  const accessLog = `---PUT request--- ${status}\n${responseStr}\n`;
  if(status >= 300){
    engine.log(accessLog);
    throw `Failed to upload. status: ${status}`;
  }else if(status === 202){
    range += packetSize;
    return range;
  }else{
    outputDataSet(status,responseStr,uploadedFileUrl);
    return range;
  }
}

/**
  * ログを出力し、アップロードしたデータのURLを配列にセットする。
  * @param {Number} status  送信時のステータスコード
  * @param {String} responseStr  送信時のレスポンスをテキスト出力したもの
  * @param {Array<String>} uploadedFileUrl  アップロードしたファイルのURLを格納する配列
  */
function outputDataSet(status,responseStr,uploadedFileUrl){
  const accessLog = `---PUT request--- ${status}\n${responseStr}\n`;
  engine.log(accessLog);
  const json = JSON.parse(responseStr);
  uploadedFileUrl.push(json.webUrl);
}
/**
  * アップロードしたデータのURLをデータ項目に出力する。
  * @param {ProcessDataDefinitionView} dataDef  データ項目の ProcessDataDefinitionView
  * @param {Array<String>} uploadedFileUrl  アップロードしたファイルのURLを格納する配列
  */
function setData(dataDef,uploadedFileUrl){
  if(dataDef !==  null){
    engine.setData(dataDef,uploadedFileUrl.join('\n'));
  }
}

Download

Capture

Notes

  1. Microsoft 365 の OneDrive for Business で使用できるアドオンです。個人用の OneDrive では使用できません。
  2. ファイルやフォルダの URL は、OneDrive でファイルやフォルダの詳細ウィンドウ(右上のiのアイコン)から「その他の詳細」へ進み、「パス」の隣のアイコンから取得します。(上部メニューの「共有」や「リンクのコピー」から取得した URL も使用できます)
    ファイル、フォルダURLの取得方法
  3. ファイル名が競合(コンフリクト)する場合は、ファイルは新しいものに置き換えられます。
  4. 4MBよりも大きなファイルは10MiBごとに分割して送信されます。この場合必要な通信の回数は(ファイルサイズ÷10MiB)+1 となります(カッコ内は切り上げ)。
%d人のブロガーが「いいね」をつけました。