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

// 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)
        .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)
        .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'));
    }
}
  
    

Download

(C) Questetra, Inc. (MIT License)
https://support.questetra.com/ja/addons/services/box-upload/
Addonファイルのインポートは Professional もしくは Enterprise でのみご利用いただけます

Notes

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

Capture

See also

エラー境界イベント

%d人のブロガーが「いいね」をつけました。