日本時間5月16日のContent Cloud Summitで、カスタムアプリにBox AI APIを活用する方法を紹介します。

詳細を表示

フォルダ内のすべてのファイルのアップロード

ガイド アップロード 分割アップロード フォルダ内のすべてのファイルのアップロード

フォルダ内のすべてのファイルのアップロード

アプリケーションによっては、1つのフォルダのすべてのファイルをアップロードできる場合もあります。SDKとCLIを使用してこの処理を実行するには、フォルダツリー内を移動してすべてのファイルを探し、そのファイルをアップロードする必要があります。

.NET
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Box.V2;
using Box.V2.Auth;
using Box.V2.Config;
using Box.V2.Converter;
using Box.V2.Exceptions;
using Box.V2.JWTAuth;
using Box.V2.Models;
using Newtonsoft.Json;

namespace BoxPlayground {
  public class Program {
    static void Main (string[] args) {
      ExecuteMainAsync ().Wait ();
    }
    const long CHUNKED_UPLOAD_MINIMUM = 200000;
    private static async Task ExecuteMainAsync () {
      var directoryName = "dotnetUploadFolder";
      var parentFolderId = "0";
      var files = Directory.EnumerateFiles (directoryName);
      using (FileStream fs = new FileStream ("./config.json", FileMode.Open)) {
        var session = new BoxJWTAuth (BoxConfig.CreateFromJsonFile (fs));
        var client = session.AdminClient (session.AdminToken ());
        var folderId = "";
        try {
          var createdFolder = await client.FoldersManager.CreateAsync (
            new BoxFolderRequest {
              Parent = new BoxRequestEntity {
                  Id = parentFolderId
                },
                Name = directoryName
            });
          folderId = createdFolder.Id;
        } catch (BoxConflictException<BoxFolder> e) {
          folderId = e.ConflictingItems.FirstOrDefault ().Id;
          System.Console.WriteLine ($"Found existing folder: {folderId}");
        }
        var fileUploadTasks = new List<Task<BoxFile>> ();
        foreach (var file in files) {
          fileUploadTasks.Add (Task.Run (
            async () => {
              System.Console.WriteLine (file);
              var fileName = file.Split (Path.DirectorySeparatorChar)
                .Where ((item) => { return item != directoryName; }).FirstOrDefault ();
              System.Console.WriteLine (fileName);
              var fileInfo = new FileInfo (file);
              var preflightRequest = new BoxPreflightCheckRequest {
                Name = fileName,
                Size = fileInfo.Length,
                Parent = new BoxRequestEntity {
                Id = folderId
                }
              };
              using (FileStream toUpload = new FileStream (file, FileMode.Open)) {
                try {
                  var preflightCheck = await client.FilesManager.PreflightCheck (preflightRequest);
                  if (toUpload.Length < CHUNKED_UPLOAD_MINIMUM) {
                    using (SHA1 sha1 = SHA1.Create ()) {
                      var fileUploadRequest = new BoxFileRequest {
                      Name = fileName,
                      Parent = new BoxRequestEntity {
                      Id = folderId
                      }
                      };
                      var fileSHA = sha1.ComputeHash (toUpload);
                      System.Console.WriteLine (fileSHA);
                      return await client.FilesManager.UploadAsync (fileRequest: fileUploadRequest, stream: toUpload, contentMD5: fileSHA);
                    }
                  } else {
                    return await client.FilesManager.UploadUsingSessionAsync (stream: toUpload, fileName: fileName, folderId: folderId);
                  }
                } catch (BoxPreflightCheckConflictException<BoxFile> e) {
                  if (toUpload.Length < CHUNKED_UPLOAD_MINIMUM) {
                    using (SHA1 sha1 = SHA1.Create ()) {
                      var fileSHA = sha1.ComputeHash (toUpload);
                      return await client.FilesManager.UploadNewVersionAsync (fileName: e.ConflictingItem.Name, fileId: e.ConflictingItem.Id, stream: toUpload, contentMD5: fileSHA);
                    }
                  } else {
                    await client.FilesManager.UploadFileVersionUsingSessionAsync (fileId: e.ConflictingItem.Id, stream: toUpload);
                    return await client.FilesManager.GetInformationAsync (e.ConflictingItem.Id);
                  }
                }
              }

            }));
        }

        var uploaded = await Task.WhenAll (fileUploadTasks);
        foreach (var file in uploaded) {
          System.Console.WriteLine (file.Id);
        }
      }
    }
  }
}

public class UploadAllFilesInFolder {

 public static final int CHUNKED_UPLOAD_MINIMUM = 20000;

 public static void main(String[] args) throws Exception {
  String directoryName = "javaUploadFolder";
  Path configPath = Paths.get("config.json");
  Path uploadFolderPath = Paths.get(directoryName);
  try (BufferedReader reader = Files.newBufferedReader(configPath, Charset.forName("UTF-8"))) {
   BoxConfig boxConfig = BoxConfig.readFrom(reader);
   BoxDeveloperEditionAPIConnection client = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(boxConfig);
   String parentFolderId = "0";
   String createdFolderId;
   BoxFolder createFolderInParentFolder = new BoxFolder(client, parentFolderId);
   try {
    BoxFolder.Info createdFolder = createFolderInParentFolder.createFolder(directoryName);
    System.out.println("Creating folder...");
    System.out.println(createdFolder.getID());
    createdFolderId = createdFolder.getID();
   } catch (BoxAPIException e) {
    String existingFolderId = getIdFromConflict(e.getMessage());
    System.out.println("Found existing folder...");
    System.out.println(existingFolderId);
    createdFolderId = existingFolderId;
   }
   ArrayList < BoxFile.Info > uploadedFiles = new ArrayList < > ();
   try (DirectoryStream < Path > directory = Files.newDirectoryStream(uploadFolderPath)) {

    for (Path path: directory) {
     String fileName = path.getFileName().toString();
     System.out.println(path);
     System.out.println(fileName);
     byte[] fileBytes = Files.readAllBytes(path);
     int fileSize = fileBytes.length;
     boolean useChunkedUpload = (fileSize > CHUNKED_UPLOAD_MINIMUM) ? true : false;
     uploadedFiles.add(uploadEachFile(client, createdFolderId, fileName, fileSize, fileBytes, useChunkedUpload));
    }
   }
   for (BoxFile.Info file: uploadedFiles) {
    System.out.println(file.getID());
   }
  }
 }

 private static BoxFile.Info uploadEachFile(BoxDeveloperEditionAPIConnection client, String folderId, String fileName,
  int fileSize, byte[] fileBytes, boolean useChunkedUpload)
 throws IOException, InterruptedException, NoSuchAlgorithmException {
  try {
   BoxFolder folder = new BoxFolder(client, folderId);
   folder.canUpload(fileName, fileSize);
   if (useChunkedUpload) {
    System.out.println("Using chunked upload...");
    return folder.uploadLargeFile(new ByteArrayInputStream(fileBytes), fileName, fileSize);
   } else {
    System.out.println("Using normal upload...");
    MessageDigest md = MessageDigest.getInstance("SHA-1");
    try (Formatter formatter = new Formatter()) {
     for (byte b: md.digest(fileBytes)) {
      formatter.format("%02x", b);
     }
     String fileSHA = formatter.toString();
     FileUploadParams fileUpload = new FileUploadParams();
     fileUpload.setContent(new ByteArrayInputStream(fileBytes));
     fileUpload.setSHA1(fileSHA);
     fileUpload.setName(fileName);
     return folder.uploadFile(fileUpload);
    }
   }
  } catch (BoxAPIException e) {
   if (e.getResponseCode() == 409) {
    // You can use the ID returned from the conflict error to continue
    String conflictId = getIdFromConflict(e.getResponse());
    System.out.println("Found existing file: " + conflictId);
    BoxFile uploadFileVersion = new BoxFile(client, conflictId);
    if (useChunkedUpload) {
     System.out.println("Using chunked upload...");
     return uploadFileVersion.uploadLargeFile(new ByteArrayInputStream(fileBytes), fileSize);
    } else {
     System.out.println("Using normal upload...");
     MessageDigest md = MessageDigest.getInstance("SHA-1");
     try (Formatter formatter = new Formatter()) {
      for (byte b: md.digest(fileBytes)) {
       formatter.format("%02x", b);
      }
      String fileSHA = formatter.toString();
      uploadFileVersion.uploadVersion(new ByteArrayInputStream(fileBytes), fileSHA);
      return uploadFileVersion.getInfo();
     }
    }
   } else {
    throw e;
   }
  }
 }

 private static String getIdFromConflict(String message) {
  String id = "";
  Pattern p = Pattern.compile("\"id\":\"[0-9]+\"");
  Pattern p2 = Pattern.compile("[0-9]+");
  Matcher m = p.matcher(message);
  if (m.find()) {
   String sub = m.group();
   Matcher m2 = p2.matcher(sub);
   if (m2.find()) {
    id = m2.group();
   }
  }
  return id;
 }
}
Node
"use strict";
const fs = require("fs");
const path = require("path");
const box = require("box-node-sdk");
const crypto = require("crypto");

let configFile = fs.readFileSync("config.json");
configFile = JSON.parse(configFile);

let session = box.getPreconfiguredInstance(configFile);
let client = session.getAppAuthClient("enterprise");

const CHUNKED_UPLOAD_MINIMUM = 200000;

const parentFolderId = "0";
const directoryName = "uploadFolder";
let files = [];

fs.readdirSync(directoryName).forEach(file => {
  files.push({
    fileName: file,
    content: fs.readFileSync(path.join(__dirname, directoryName, file))
  });
});

client.folders
  .create(parentFolderId, directoryName)
  .then(createdFolder => {
    console.log(createdFolder);
    return processFiles(client, files, createdFolder.id);
  })
  .catch(err => {
    let conflictId = handleFolderConflictError(err);
    if (conflictId) {
      console.log(`Found an existing folder: ${conflictId}`);
      return processFiles(client, files, conflictId);
    } else {
      throw err;
    }
  })
  .then(results => {
    console.log(results);
  })
  .catch(err => {
    console.log(err);
  });

function processFiles(client, files, folderId) {
  let fileUploadPromises = [];
  files.forEach(file => {
    fileUploadPromises.push(
      uploadAFile(client, folderId, file.fileName, file.content)
    );
  });

  return Promise.all(fileUploadPromises);
}

function uploadAFile(client, folderId, fileName, toUploadFile) {
  return client.files
    .preflightUploadFile(folderId, {
      name: fileName,
      size: toUploadFile.length
    })
    .then(preflightResults => {
      console.log(preflightResults);
      if (toUploadFile.length < CHUNKED_UPLOAD_MINIMUM) {
        console.log("Using normal upload...");
        let fileSha = crypto
          .createHash("sha1")
          .update(toUploadFile)
          .digest("hex");
        client.setCustomHeader("content-md5", fileSha);
        return client.files.uploadFile(folderId, fileName, toUploadFile);
      } else {
        console.log("Using chunked upload...");
        client.setCustomHeader("content-md5", null);
        return client.files
          .getChunkedUploader(
            folderId,
            toUploadFile.length,
            fileName,
            toUploadFile
          )
          .then(uploader => {
            return new Promise((resolve, reject) => {
              uploader.on("error", err => {
                reject(err);
              });

              uploader.on("chunkUploaded", part => {
                console.log("Part uploaded...");
                console.log(part);
              });
              uploader.on("uploadComplete", file => {
                console.log("File upload complete!");
                resolve(file);
              });
              console.log("Starting chunked uploader...");
              uploader.start();
            });
          });
      }
    })
    .catch(err => {
      let conflictId = handleFileConflictError(err);
      if (conflictId) {
        console.log(`Found existing file with that name: ${conflictId}`);
        return uploadANewFileVersion(client, conflictId, toUploadFile);
      } else {
        throw err;
      }
    });
}

function uploadANewFileVersion(client, conflictId, toUploadFile) {
  if (toUploadFile.length < CHUNKED_UPLOAD_MINIMUM) {
    console.log("Using normal upload...");
    let fileSha = crypto
      .createHash("sha1")
      .update(toUploadFile)
      .digest("hex");
    client.setCustomHeader("content-md5", fileSha);
    // You can optionally rename a folder while uploading a new version.
    // let newFileName = "ubuntu-no-gui.iso";
    // let options = {
    //     name: newFileName
    // }
    // return client.files.uploadNewFileVersion(conflictId, options, toUploadFile);
    return client.files.uploadNewFileVersion(conflictId, toUploadFile);
  } else {
    console.log("Using chunked upload...");
    // You can optionally rename a folder while uploading a new version.
    // let newFileName = "ubuntu-no-gui.iso";
    // let options = {
    //     name: newFileName
    // }
    // return client.files.getNewVersionChunkedUploader(conflictId, toUploadFile.length, toUploadFile, options)
    client.setCustomHeader("content-md5", null);
    return client.files
      .getNewVersionChunkedUploader(
        conflictId,
        toUploadFile.length,
        toUploadFile,
        null
      )
      .then(uploader => {
        return new Promise((resolve, reject) => {
          uploader.on("error", err => {
            reject(err);
          });

          uploader.on("chunkUploaded", part => {
            console.log("Part uploaded...");
            console.log(part);
          });
          uploader.on("uploadComplete", file => {
            console.log("File upload complete!");
            resolve(file);
          });
          console.log("Starting chunked uploader...");
          uploader.start();
        });
      });
  }
}

function handleFileConflictError(e) {
  if (e && e.response && e.response.body) {
    let errorBody = e.response.body;
    if (errorBody.status === 409) {
      if (
        errorBody.context_info &&
        errorBody.context_info.conflicts &&
        errorBody.context_info.conflicts
      ) {
        let conflict = errorBody.context_info.conflicts;
        if (conflict && conflict.id) {
          return conflict.id;
        }
      }
    }
  }
}

function handleFolderConflictError(e) {
  if (e && e.response && e.response.body) {
    let errorBody = e.response.body;
    if (errorBody.status === 409) {
      if (
        errorBody.context_info &&
        errorBody.context_info.conflicts &&
        errorBody.context_info.conflicts.length > 0
      ) {
        let conflict = errorBody.context_info.conflicts[0];
        if (conflict && conflict.id) {
          return conflict.id;
        }
      }
    }
  }
}
CLI
box folders:upload ./folder_name_to_upload --parent-folder=$folder_id

解説

上のスクリプトは、Box SDKとCLIを使用してフォルダ全体をアップロードします。このSDKスクリプトでは、最初にローカルフォルダに対応するディレクトリをBox内に作成します。

新しいディレクトリが作成されたら、ディレクトリ内のすべてのファイルをアップロードし、利用可能なすべてのBox機能を使用してアップロードを確実に成功させます。

アップロードの前に、事前チェックAPIを使用してファイルの競合とサイズ制限をチェックします。名前の競合が見つかった場合、代わりにそのファイルの新しいバージョンをアップロードします。

ファイルのSHAハッシュを使用して、アップロード時にcontent-md5ヘッダーを追加することで、ファイルのデータが失われたり改変されたりすることなくBoxに正常にアップロードされるようにします。

最後に、ファイルサイズが20 MBを超える場合、分割アップロード機能を使用して、大きいファイルのアップロードの信頼性を高めます。