private static final String
BOX_UPLOAD_URL = "https://upload.box.com/api/2.0/files/content",
CREATE_SESSION_URL = "https://upload.box.com/api/2.0/files/upload_sessions",
UPLOAD_SESSION_URL_TEMPLATE = "https://upload.box.com/api/2.0/files/upload_sessions/%s",
COMMIT_SESSION_URL_TEMPLATE = "https://upload.box.com/api/2.0/files/upload_sessions/%s/commit";
private static final MediaType
JSON = MediaType.get("application/json; charset=utf-8"),
OCTET = MediaType.get("application/octet-stream");
private static final String[] REQUESTED_FIELDS = {
"name",
"size",
"content_modified_at",
"parent",
"item_status",
"trashed_at"
};
public com.box.sdk.BoxFile.Info uploadChunked(InputStream is, String fileName, long totalSize, Date lastModified, String parentFolderId, String accessToken, BoxAPIConnection api, IProgressListener progressListener) throws Exception {
if (totalSize <= 0)
throw new IllegalArgumentException("totalSize must be known and >0 for chunked upload");
OkHttpClient client = new OkHttpClient();
JSONObject createPayload = new JSONObject();
createPayload.put("folder_id", parentFolderId);
createPayload.put("file_name", fileName);
createPayload.put("file_size", totalSize);
Request createReq = new Request.Builder()
.url(CREATE_SESSION_URL)
.addHeader("Authorization", "Bearer " + accessToken)
.post(RequestBody.create(createPayload.toString(), JSON))
.build();
try (Response resp = client.newCall(createReq).execute()) {
if (!resp.isSuccessful()) {
String body = resp.body() != null ? resp.body().string() : null;
throw new IOException("Failed to create upload session: " + resp.code() + " body=" + body);
}
String respBody = resp.body().string();
JSONObject sessionJson = new JSONObject(respBody);
String sessionId = sessionJson.getString("id");
long partSize = sessionJson.optLong("part_size", 8 * 1024 * 1024); // fallback 8MB
return uploadPartsAndCommit(is, fileName, totalSize, lastModified, accessToken, progressListener, sessionId, partSize, client, api);
}
}
private com.box.sdk.BoxFile.Info uploadPartsAndCommit(InputStream is, String fileName, long totalSize, Date lastModified, String accessToken, IProgressListener progressListener, String sessionId, long partSize, OkHttpClient client, BoxAPIConnection api) throws Exception {
List<JSONObject> uploadedParts = new ArrayList<>();
MessageDigest wholeDigest = MessageDigest.getInstance("SHA-1");
byte[] buffer = new byte[(int) partSize];
long offset = 0;
int read;
long uploaded = 0;
while (offset < totalSize) {
int toRead = (int) Math.min(partSize, totalSize - offset);
int actuallyRead = 0;
int pos = 0;
while (pos < toRead && (read = is.read(buffer, pos, toRead - pos)) != -1) {
pos += read;
}
actuallyRead = pos;
if (actuallyRead <= 0) {
throw new IOException("Unexpected EOF: expected " + toRead + " but read " + actuallyRead);
}
wholeDigest.update(buffer, 0, actuallyRead);
MessageDigest partDigest = MessageDigest.getInstance("SHA-1");
partDigest.update(buffer, 0, actuallyRead);
String partShaBase64 = Base64.encodeToString(partDigest.digest(), Base64.NO_WRAP);
long start = offset;
long end = offset + actuallyRead - 1;
int finalActuallyRead = actuallyRead;
RequestBody partBody = new RequestBody() {
@Override
public MediaType contentType() {
return OCTET;
}
@Override
public long contentLength() {
return finalActuallyRead;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.write(buffer, 0, finalActuallyRead);
}
};
Request uploadPartReq = new Request.Builder()
.url(String.format(UPLOAD_SESSION_URL_TEMPLATE, sessionId))
.addHeader("Authorization", "Bearer " + accessToken)
.addHeader("Content-Type", "application/octet-stream")
.addHeader("Content-Range", "bytes " + start + "-" + end + "/" + totalSize)
.addHeader("Digest", "SHA=" + partShaBase64)
.put(partBody)
.build();
try (Response uploadResp = client.newCall(uploadPartReq).execute()) {
if (!uploadResp.isSuccessful()) {
String b = uploadResp.body() != null ? uploadResp.body().string() : null;
throw new IOException("Part upload failed: code=" + uploadResp.code() + " body=" + b);
}
String partRespBody = uploadResp.body() != null ? uploadResp.body().string() : null;
if (partRespBody == null)
throw new IOException("Empty part response from server");
JSONObject partRespJson = new JSONObject(partRespBody);
JSONObject partObj = partRespJson.optJSONObject("part");
if (partObj == null)
partObj = partRespJson;
uploadedParts.add(partObj);
}
offset += actuallyRead;
uploaded += actuallyRead;
if (progressListener != null)
progressListener.onProgress(uploaded, totalSize);
}
String wholeBase64 = Base64.encodeToString(wholeDigest.digest(), Base64.NO_WRAP);
DateTimeFormatter ISO_8601_NO_MILLIS_UTC_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneId.of("UTC"));
String isoModified = ISO_8601_NO_MILLIS_UTC_FORMATTER.format(lastModified.toInstant());
JSONObject commitPayload = new JSONObject();
JSONArray partsArray = new JSONArray();
for (JSONObject p : uploadedParts)
partsArray.put(p);
commitPayload.put("parts", partsArray);
JSONObject attributes = new JSONObject();
attributes.put("content_modified_at", isoModified);
commitPayload.put("attributes", attributes);
RequestBody commitBody = RequestBody.create(commitPayload.toString(), JSON);
Request commitReq = new Request.Builder()
.url(String.format(COMMIT_SESSION_URL_TEMPLATE, sessionId))
.addHeader("Authorization", "Bearer " + accessToken)
.addHeader("Digest", "SHA=" + wholeBase64)
.post(commitBody)
.build();
try (Response commitResp = client.newCall(commitReq).execute()) {
String commitRespBody = commitResp.body() != null ? commitResp.body().string() : null;
if (!commitResp.isSuccessful())
throw new IOException("Commit failed: code=" + commitResp.code() + " body=" + commitRespBody);
FinalResp finalResp = new Gson().fromJson(commitRespBody, FinalResp.class);
if (!finalResp.entries.isEmpty()) {
BoxFileInfo boxFileInfo = finalResp.entries.get(0);
com.box.sdk.BoxFile file = new com.box.sdk.BoxFile(api, boxFileInfo.id);
return file.getInfo(REQUESTED_FIELDS);
}
return null;
}
}
public class ApiResponse {
@SerializedName("total_count")
public int totalCount;
@SerializedName("entries")
public List<Entry> entries;
}
public class BoxFileInfo {
public String id;
public String name;
public long size;
public String content_modified_at;
public String created_at;
public String modified_at;
}
public class BoxFileInfo {
public String id;
public String name;
public long size;
public String content_modified_at;
public String created_at;
public String modified_at;
}
public class Entry {
@SerializedName("type")
public String type;
@SerializedName("id")
public String id;
@SerializedName("file_version")
public FileVersion fileVersion;
@SerializedName("sequence_id")
public String sequenceId;
@SerializedName("etag")
public String etag;
@SerializedName("sha1")
public String sha1;
@SerializedName("name")
public String name;
@SerializedName("description")
public String description;
@SerializedName("size")
public long size;
@SerializedName("path_collection")
public PathCollection pathCollection;
@SerializedName("created_at")
public String createdAt;
@SerializedName("modified_at")
public String modifiedAt;
@Nullable
@SerializedName("trashed_at")
public String trashedAt; // Может быть null
@Nullable
@SerializedName("purged_at")
public String purgedAt; // Может быть null
@SerializedName("content_created_at")
public String contentCreatedAt;
@SerializedName("content_modified_at")
public String contentModifiedAt;
@SerializedName("created_by")
public User createdBy;
@SerializedName("modified_by")
public User modifiedBy;
@SerializedName("owned_by")
public User ownedBy;
@Nullable
@SerializedName("shared_link")
public String sharedLink; // Может быть null
@SerializedName("parent")
public Parent parent;
@SerializedName("item_status")
public String itemStatus;
}
public class FinalResp {
public List<BoxFileInfo> entries;
}
I am using com.box:box-java-sdk:5.0.0. The BoxFolder class has a method called
BoxFile.Info uploadFile(InputStream fileContent, String name, long fileSize, ProgressListener listener)which doesn't provide the ability to set the time of file creation. This means that when uploading a file from another file system via InputStream that was created a long time ago, the file that appears in the cloud is a newly created file. This is incorrect and confuses users. You need to add a method with the signature
BoxFile.Info uploadFile(InputStream fileContent, String name, Date lastModified, long fileSize, ProgressListener listener).Moreover, your API has a similar method called
BoxFile.Info uploadNewVersion(InputStream fileContent, Date modified, long fileSize, ProgressListener listener),but it only applies to updating an existing file, not uploading a new one.Here's some sample code you could add to your library com.box:box-java-sdk:5.0.0. to support the user setting the date when uploading a file via a InputStream: