Box: ファイルアップロード (Box: Upload File)
この工程は、Box の指定フォルダにファイルをアップロードします。
Configs:共通設定
  • 工程名
  • メモ
Configs
  • C1: OAuth2 設定 *
  • C2: アップロードするファイルが保存されているファイル型データ項目 *
  • C3: ファイルをアップロードするフォルダの ID (指定がない場合は、ルートフォルダ)#{EL}
  • C4: ファイル ID を保存する文字型データ項目
  • C5: ファイル URL を保存する文字型データ項目

Notes

  • フォルダ ID は、URL に含まれています。https://{sub-domain}.app.box.com/folder/(Folder ID)
  • ファイル名が競合(コンフリクト)する場合はエラーとなります
    • 競合したファイルが 50MB より大きい場合は、すべてのファイルがアップロードされません
    • 競合したファイルが 50MB 以下の場合は、それより前にセットされているファイルがアップロードされます
  • Box のリフレッシュトークンには、期限があります
  • 50MB よりも大きなファイルは Box の指定するサイズに分割して送信されます
    • この場合、必要な通信の回数は(ファイルサイズ÷指定サイズ)+2 となります(カッコ内は切り上げ)

Capture

See also

Script (click to open)
  • 下記のスクリプトを記述した XML ファイルをダウンロードできます
    • box-file-upload.xml (C) Questetra, Inc. (MIT License)
    • Professional をご利用であればファイルの内容を改変することでオリジナルのアドオンとして活用できます

// OAuth2 config
// Authorization Endpoint URL: https://app.box.com/api/oauth2/authorize
// Token Endpoint URL: https://app.box.com/api/oauth2/token
// Scope:
// Consumer Key: (Get by box Application on box Developers)
// Consumer Secret: (Get by box Application on box Developers)

const SIZE_LIMIT = 52428800; //File size border of Box
//50MB

main();

function main() {
    //// == 工程コンフィグの参照 / Config Retrieving ==
    const oauth2 = configs.get("conf_OAuth2");
    const idDataDef = configs.getObject("fileId");
    const urlDataDef = configs.getObject("fileUrl");
    //// == ワークフローデータの参照 / Data Retrieving ==
    const folderId = decideFolderId();
    const files = engine.findData(configs.getObject("uploadedFile"));
    if (files === null) {
        setData(idDataDef, [""]);
        setData(urlDataDef, [""]);
        return;
    }

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

    checkFile(files, idDataDef, urlDataDef);
    let uploadedFileData = [[], []]; //0:ID,1:URL
    const session = prepareSessionsAndCheckRequestNumber(oauth2, files, folderId);
    uploadAllFiles(oauth2, files, folderId, uploadedFileData, session);

    //// == ワークフローデータへの代入 / Data Updating ==
    setData(idDataDef, uploadedFileData[0]);
    setData(urlDataDef, uploadedFileData[1]);
}

/**
 * フォルダのIDをconfigから読み出して出力する。空の場合は"0"とする。
 * @return {String}  フォルダのID
 */
function decideFolderId() {
    let folderId = configs.get("uploadedFolderId");
    if (folderId === "" || folderId === null) {
        //when folder ID isn't set, set "0"
        folderId = "0";
    }
    return folderId;
}

/**
 * アップロードしようとするファイルの数・名前が適切かどうかチェックする
 * ファイル数が通信制限を超えたらエラーを出し、その後各関数を呼び出す
 * @param {Array<File>} files  アップロードしようとするファイルの配列
 * @param {String} folderId  アップロード先フォルダのID
 * @param {ProcessDataDefinitionView} idDataDef  ID を保存するデータ項目の ProcessDataDefinitionView
 * @param {ProcessDataDefinitionView} urlDataDef  URL を保存するデータ項目の ProcessDataDefinitionView
 */
function checkFile(files, idDataDef, urlDataDef) {
    const fileNum = files.size(); //number of files
    if (fileNum > httpClient.getRequestingLimit()) {
        throw "Number of requests is over the limit";
    }
    checkfileNum(idDataDef, fileNum);
    checkfileNum(urlDataDef, fileNum);
    checkFileName(files);
    checkFileNameOverlap(files);
}

/**
 * アップロードするファイルが複数で パス・URL 出力先のデータ項目が単一行ならエラーにする
 * @param {ProcessDataDefinitionView} dataDef  データ項目の ProcessDataDefinitionView
 * @param {Number} fileNum  アップロードしようとするファイルの個数
 */
function checkfileNum(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."
        }
    }
}

/**
 * アップロードするファイルの名前がBoxにおいて無効なファイル名となるものならエラーにする
 * Boxにおいて無効なファイル名となる条件
 * ・ファイル名に\ / のいずれかが含まれている
 * ・ファイル名の先頭か末尾にスペースがある
 * ・ファイル名が. または .. である
 * @param {Array<File>} files  アップロードしようとするファイルの配列
 */
function checkFileName(files) {
    for (let i = 0; i < files.size(); i++) {
        let fileName = files[i].getName();
        if (fileName.indexOf("/") !== -1 || fileName.indexOf("\\") !== -1 || fileName === "." || fileName === ".." || fileName.endsWith(" ") === true || fileName.startsWith(" ") === true) {
            throw "Invalid File Name";
        }
    }
}

/**
 * アップロードするファイルの中に同じファイル名のものが2つ以上あればエラー
 * @param {Array<File>}  アップロードしようとするファイルの配列
 */
function checkFileNameOverlap(files) {
    let fileNames = [];
    const fileNum = files.size();
    for (let i = 0; i < fileNum; i++) {
        if (fileNames.includes(files[i].getName())) {
            throw "Two or more files to upload have the same name.";
        }
        fileNames[i] = files[i].getName();
    }
}

/**
 * 50MBを超えるファイルについてセッションを作成し、通信数を確認する
 * 通信数が制限を超えたらエラーを出す
 * 通信数=50MB以下のファイルの数 + <50MBを超えるファイルそれぞれについて>(ceil(ファイルサイズ/パケットサイズ) + 2)
 * パケットサイズはセッション作成時に指定される
 * @param {String} oauth2  OAuth2 設定
 * @param {Array<File>} files  アップロードしようとするファイルの配列
 * @param {String} folderId  アップロード先フォルダのID
 * @return {Array<Object>} アップロードセッションの配列(50MB以下のファイルについては空オブジェクト)
 */
function prepareSessionsAndCheckRequestNumber(oauth2, files, folderId) {
    let requestNum = 0;
    let session = [];
    for (let i = 0; i < files.size(); i++) {
        requestNum++;
        if (files.get(i).getLength() > SIZE_LIMIT) {
            //over 50MB
            session[i] = createSession(oauth2, files.get(i), folderId);
            requestNum += (Math.ceil(files.get(i).getLength() / session[i].part_size + 1));
        } else {
            session[i] = {};
        }
    }
    if (requestNum > httpClient.getRequestingLimit()) {
        throw "Number of requests is over the limit";
    }
    return session;
}

/**
 * 各ファイルのアップロード処理を行う
 * ファイルのサイズごとに工程は異なる
 * @param {String} oauth2  OAuth2 設定
 * @param {Array<File>} files  アップロードしようとするファイルの配列
 * @param {String} folderId  アップロード先のフォルダのパス
 * @param {Array<Array<String>>} uploadedFileData  アップロードしたファイルの情報を格納する二次元配列
 * @param {Array<Object>} session アップロードセッションの配列(50MB以下のファイルについては空オブジェクト)
 */
function uploadAllFiles(oauth2, files, folderId, uploadedFileData, session) {
    for (let i = 0; i < files.size(); i++) {
        engine.log(files.get(i).getName());
        if (files.get(i).getLength() > SIZE_LIMIT) {
            //over 50MB
            processLargeFile(oauth2, files.get(i), folderId, uploadedFileData, session[i]);
        } else {
            //under 50MB
            upload(oauth2, files.get(i), folderId, uploadedFileData);
        }
    }
}

/**
 * 50MB以下のファイルをアップロードする。一回につき一つのみ。
 * @param {String} oauth2  OAuth2 設定
 * @param {File} file  アップロードするファイル
 * @param {String} folderId  アップロード先フォルダのID
 * @param {Array<Array<String>>} uploadedFileData  アップロードしたファイルの情報を格納する二次元配列
 */
function upload(oauth2, file, folderId, uploadedFileData) {
    const url = "https://upload.box.com/api/2.0/files/content";
    let attributes = {
        parent: {id: folderId}
    };
    attributes["name"] = String(file.getName());

    let response = httpClient.begin()
        .authSetting(oauth2)
        .multipart('attributes', JSON.stringify(attributes))
        .multipart('file', file)
        .queryParam("fields", "id")
        .post(url);
    //when error thrown
    const responseStr = response.getResponseAsString();
    const status = response.getStatusCode();
    if (status >= 300) {
        engine.log(` upload failure\n${responseStr}`);
        throw `failed to upload\nstatus: ${status}\nfile: ${file.getName()}`;
    }
    engine.log(" upload successful");
    addOutputToArray(responseStr, uploadedFileData);
}

/**
 * 50MBを超えるファイルのアップロード処理を行う
 * @param {String} oauth2  OAuth2 設定
 * @param {File} file  アップロードするファイル
 * @param {String} folderId  アップロード先のフォルダのパス
 * @param {Array<Array<String>>} uploadedFileData  アップロードしたファイルの情報を格納する二次元配列
 * @param {Object} session  アップロードセッション
 */
function processLargeFile(oauth2, file, folderId, uploadedFileData, session) {
    const sessionId = session.id;
    const fileSize = file.getLength();
    let part;
    let partObj = {"parts": []};
    let range = 0;
    fileRepository.readFile(file, session.part_size, function (packet) {
        //upload each fragment of file
        part = uploadLargeFile(oauth2, sessionId, range, packet, fileSize, uploadedFileData, file.getName());
        partObj.parts.push(part);
        range += part.size;
    });
    commitSession(oauth2, sessionId, partObj, file, uploadedFileData);
}

/**
 * アップロード用のセッションを作成する
 * @param {String} oauth2  OAuth2 設定
 * @param {File} file  アップロードするファイル
 * @param {String} folderId  アップロード先のフォルダのID
 * @return {JSON Object}  アップロードセッション
 */
function createSession(oauth2, file, folderId) {
    const url = "https://upload.box.com/api/2.0/files/upload_sessions";
    const body = {
        "file_name": file.getName(),
        "file_size": file.getLength(),
        "folder_id": folderId
    };
    let response = httpClient.begin()
        .authSetting(oauth2)
        .body(JSON.stringify(body), "application/json; charset=UTF-8")
        .post(url);

    const status = response.getStatusCode();
    const jsonStr = response.getResponseAsString();
    if (status >= 300) {
        engine.log(file.getName())
        engine.log(` upload failure\n${jsonStr}`);
        throw `failed to create upload session \nstatus: ${status}\nfile: ${file.getName()}`;
    }
    return JSON.parse(jsonStr);
}

/**
 * 50MBを超えるファイルについて、各部分のアップロードを実行する
 * @param {String} oauth2  OAuth2 設定
 * @param {String} sessionId  アップロードセッションの ID
 * @param {Number} range  range
 * @param {ByteArrayWrapper} packet  アップロードするバイナリ
 * @param {Number} fileSize  アップロードするファイルのサイズ
 * @param {Array<Array<String>>} uploadedFileData  アップロードしたファイルの情報を格納する二次元配列
 * @return {Object}  Uploaded Part
 */
function uploadLargeFile(oauth2, sessionId, range, packet, fileSize, uploadedFileData, fileName) {
    const packetSize = packet.getLength();
    const url = `https://upload.box.com/api/2.0/files/upload_sessions/${sessionId}`;
    const rangetxt = `bytes ${range}-${range + packetSize - 1}/${fileSize}`;
    const hash = `sha=${base64.encodeToString(digest.sha1(packet))}`;
    let sending = httpClient.begin()
        .authSetting(oauth2)
        .header("Content-Range", rangetxt)
        .header("Digest", hash)
        .body(packet, "application/octet-stream")
        .put(url);

    const status = sending.getStatusCode();
    const responseStr = sending.getResponseAsString();
    const json = JSON.parse(responseStr);
    if (status >= 300) {
        engine.log(` upload failure\n${responseStr}`);
        throw `failed to upload\nstatus: ${status}\nfile: ${fileName}`;
    } else {
        return json.part;
    }
}

/**
 * 50MBを超えるファイルについて、アップロードをコミットする
 * @param {String} oauth2  OAuth2 設定
 * @param {String} sessionId  アップロードセッションの ID
 * @param {Object} partObj  これまでのアップロートパートをまとめたオブジェクト
 * @param {File} file  アップロードするファイル
 * @param {Array<Array<String>>} uploadedFileData  アップロードしたファイルの情報を格納する二次元配列
 */
function commitSession(oauth2, sessionId, partObj, file, uploadedFileData) {
    const url = `https://upload.box.com/api/2.0/files/upload_sessions/${sessionId}/commit`;
    const hash = `sha=${base64.encodeToString(digest.sha1(file))}`;
    let commit = httpClient.begin()
        .authSetting(oauth2)
        .header("Digest", hash)
        .queryParam("fields", "id")
        .body(JSON.stringify(partObj), "application/json")
        .post(url);

    const status = commit.getStatusCode();
    const responseStr = commit.getResponseAsString();
    if (status >= 300) {
        engine.log(` upload failure\n${responseStr}`);
        throw `failed to commit upload session \nstatus: ${status}\nfile: ${file.getName()}`;
    }
    engine.log(" upload successful");
    addOutputToArray(responseStr, uploadedFileData);
}

/**
 * アップロードしたデータのIDとURLを配列にセットする。
 * @param {String} responseStr  送信時のレスポンスをテキスト出力したもの
 * @param {Array<String>} uploadedFileData  アップロードしたファイルの情報が格納される配列
 */
function addOutputToArray(responseStr, uploadedFileData) {
    const json = JSON.parse(responseStr);
    uploadedFileData[0].push(json.entries[0].id);
    uploadedFileData[1].push(`https://app.box.com/file/${json.entries[0].id}`);
}

/**
 * アップロードしたデータのパスとURLをデータ項目に出力する。
 * @param {ProcessDataDefinitionView} dataDef  データ項目の ProcessDataDefinitionView
 * @param {Array<String>} uploadedFileData  アップロードしたファイルの情報が格納されている配列
 */
function setData(dataDef, uploadedFileData) {
    if (dataDef !== null) {
        engine.setData(dataDef, uploadedFileData.join('\n'));
    }
}
  
    
%d人のブロガーが「いいね」をつけました。