001package com.box.sdkgen.managers.chunkeduploads;
002
003import static com.box.sdkgen.internal.utils.UtilsManager.bufferLength;
004import static com.box.sdkgen.internal.utils.UtilsManager.convertToString;
005import static com.box.sdkgen.internal.utils.UtilsManager.entryOf;
006import static com.box.sdkgen.internal.utils.UtilsManager.generateByteStreamFromBuffer;
007import static com.box.sdkgen.internal.utils.UtilsManager.hexToBase64;
008import static com.box.sdkgen.internal.utils.UtilsManager.iterateChunks;
009import static com.box.sdkgen.internal.utils.UtilsManager.mapOf;
010import static com.box.sdkgen.internal.utils.UtilsManager.mergeMaps;
011import static com.box.sdkgen.internal.utils.UtilsManager.prepareParams;
012import static com.box.sdkgen.internal.utils.UtilsManager.readByteStream;
013import static com.box.sdkgen.internal.utils.UtilsManager.reduceIterator;
014
015import com.box.sdkgen.internal.utils.Hash;
016import com.box.sdkgen.internal.utils.HashName;
017import com.box.sdkgen.networking.auth.Authentication;
018import com.box.sdkgen.networking.fetchoptions.FetchOptions;
019import com.box.sdkgen.networking.fetchoptions.ResponseFormat;
020import com.box.sdkgen.networking.fetchresponse.FetchResponse;
021import com.box.sdkgen.networking.network.NetworkSession;
022import com.box.sdkgen.schemas.filefull.FileFull;
023import com.box.sdkgen.schemas.files.Files;
024import com.box.sdkgen.schemas.uploadedpart.UploadedPart;
025import com.box.sdkgen.schemas.uploadpart.UploadPart;
026import com.box.sdkgen.schemas.uploadparts.UploadParts;
027import com.box.sdkgen.schemas.uploadsession.UploadSession;
028import com.box.sdkgen.serialization.json.JsonManager;
029import java.io.InputStream;
030import java.util.Arrays;
031import java.util.Collections;
032import java.util.Iterator;
033import java.util.List;
034import java.util.Map;
035import java.util.stream.Collectors;
036import java.util.stream.Stream;
037
038public class ChunkedUploadsManager {
039
040  public Authentication auth;
041
042  public NetworkSession networkSession;
043
044  public ChunkedUploadsManager() {
045    this.networkSession = new NetworkSession();
046  }
047
048  protected ChunkedUploadsManager(Builder builder) {
049    this.auth = builder.auth;
050    this.networkSession = builder.networkSession;
051  }
052
053  /**
054   * Creates an upload session for a new file.
055   *
056   * @param requestBody Request body of createFileUploadSession method
057   */
058  public UploadSession createFileUploadSession(CreateFileUploadSessionRequestBody requestBody) {
059    return createFileUploadSession(requestBody, new CreateFileUploadSessionHeaders());
060  }
061
062  /**
063   * Creates an upload session for a new file.
064   *
065   * @param requestBody Request body of createFileUploadSession method
066   * @param headers Headers of createFileUploadSession method
067   */
068  public UploadSession createFileUploadSession(
069      CreateFileUploadSessionRequestBody requestBody, CreateFileUploadSessionHeaders headers) {
070    Map<String, String> headersMap = prepareParams(mergeMaps(mapOf(), headers.getExtraHeaders()));
071    FetchResponse response =
072        this.networkSession
073            .getNetworkClient()
074            .fetch(
075                new FetchOptions.Builder(
076                        String.join(
077                            "",
078                            this.networkSession.getBaseUrls().getUploadUrl(),
079                            "/2.0/files/upload_sessions"),
080                        "POST")
081                    .headers(headersMap)
082                    .data(JsonManager.serialize(requestBody))
083                    .contentType("application/json")
084                    .responseFormat(ResponseFormat.JSON)
085                    .auth(this.auth)
086                    .networkSession(this.networkSession)
087                    .build());
088    return JsonManager.deserialize(response.getData(), UploadSession.class);
089  }
090
091  /**
092   * Creates an upload session for an existing file.
093   *
094   * @param fileId The unique identifier that represents a file.
095   *     <p>The ID for any file can be determined by visiting a file in the web application and
096   *     copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the
097   *     `file_id` is `123`. Example: "12345"
098   * @param requestBody Request body of createFileUploadSessionForExistingFile method
099   */
100  public UploadSession createFileUploadSessionForExistingFile(
101      String fileId, CreateFileUploadSessionForExistingFileRequestBody requestBody) {
102    return createFileUploadSessionForExistingFile(
103        fileId, requestBody, new CreateFileUploadSessionForExistingFileHeaders());
104  }
105
106  /**
107   * Creates an upload session for an existing file.
108   *
109   * @param fileId The unique identifier that represents a file.
110   *     <p>The ID for any file can be determined by visiting a file in the web application and
111   *     copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the
112   *     `file_id` is `123`. Example: "12345"
113   * @param requestBody Request body of createFileUploadSessionForExistingFile method
114   * @param headers Headers of createFileUploadSessionForExistingFile method
115   */
116  public UploadSession createFileUploadSessionForExistingFile(
117      String fileId,
118      CreateFileUploadSessionForExistingFileRequestBody requestBody,
119      CreateFileUploadSessionForExistingFileHeaders headers) {
120    Map<String, String> headersMap = prepareParams(mergeMaps(mapOf(), headers.getExtraHeaders()));
121    FetchResponse response =
122        this.networkSession
123            .getNetworkClient()
124            .fetch(
125                new FetchOptions.Builder(
126                        String.join(
127                            "",
128                            this.networkSession.getBaseUrls().getUploadUrl(),
129                            "/2.0/files/",
130                            convertToString(fileId),
131                            "/upload_sessions"),
132                        "POST")
133                    .headers(headersMap)
134                    .data(JsonManager.serialize(requestBody))
135                    .contentType("application/json")
136                    .responseFormat(ResponseFormat.JSON)
137                    .auth(this.auth)
138                    .networkSession(this.networkSession)
139                    .build());
140    return JsonManager.deserialize(response.getData(), UploadSession.class);
141  }
142
143  /**
144   * Using this method with urls provided in response when creating a new upload session is
145   * preferred to use over GetFileUploadSessionById method. This allows to always upload your
146   * content to the closest Box data center and can significantly improve upload speed. Return
147   * information about an upload session.
148   *
149   * <p>The actual endpoint URL is returned by the [`Create upload
150   * session`](https://developer.box.com/reference/post-files-upload-sessions) endpoint.
151   *
152   * @param url URL of getFileUploadSessionById method
153   */
154  public UploadSession getFileUploadSessionByUrl(String url) {
155    return getFileUploadSessionByUrl(url, new GetFileUploadSessionByUrlHeaders());
156  }
157
158  /**
159   * Using this method with urls provided in response when creating a new upload session is
160   * preferred to use over GetFileUploadSessionById method. This allows to always upload your
161   * content to the closest Box data center and can significantly improve upload speed. Return
162   * information about an upload session.
163   *
164   * <p>The actual endpoint URL is returned by the [`Create upload
165   * session`](https://developer.box.com/reference/post-files-upload-sessions) endpoint.
166   *
167   * @param url URL of getFileUploadSessionById method
168   * @param headers Headers of getFileUploadSessionById method
169   */
170  public UploadSession getFileUploadSessionByUrl(
171      String url, GetFileUploadSessionByUrlHeaders headers) {
172    Map<String, String> headersMap = prepareParams(mergeMaps(mapOf(), headers.getExtraHeaders()));
173    FetchResponse response =
174        this.networkSession
175            .getNetworkClient()
176            .fetch(
177                new FetchOptions.Builder(url, "GET")
178                    .headers(headersMap)
179                    .responseFormat(ResponseFormat.JSON)
180                    .auth(this.auth)
181                    .networkSession(this.networkSession)
182                    .build());
183    return JsonManager.deserialize(response.getData(), UploadSession.class);
184  }
185
186  /**
187   * Return information about an upload session.
188   *
189   * <p>The actual endpoint URL is returned by the [`Create upload
190   * session`](https://developer.box.com/reference/post-files-upload-sessions) endpoint.
191   *
192   * @param uploadSessionId The ID of the upload session. Example: "D5E3F7A"
193   */
194  public UploadSession getFileUploadSessionById(String uploadSessionId) {
195    return getFileUploadSessionById(uploadSessionId, new GetFileUploadSessionByIdHeaders());
196  }
197
198  /**
199   * Return information about an upload session.
200   *
201   * <p>The actual endpoint URL is returned by the [`Create upload
202   * session`](https://developer.box.com/reference/post-files-upload-sessions) endpoint.
203   *
204   * @param uploadSessionId The ID of the upload session. Example: "D5E3F7A"
205   * @param headers Headers of getFileUploadSessionById method
206   */
207  public UploadSession getFileUploadSessionById(
208      String uploadSessionId, GetFileUploadSessionByIdHeaders headers) {
209    Map<String, String> headersMap = prepareParams(mergeMaps(mapOf(), headers.getExtraHeaders()));
210    FetchResponse response =
211        this.networkSession
212            .getNetworkClient()
213            .fetch(
214                new FetchOptions.Builder(
215                        String.join(
216                            "",
217                            this.networkSession.getBaseUrls().getUploadUrl(),
218                            "/2.0/files/upload_sessions/",
219                            convertToString(uploadSessionId)),
220                        "GET")
221                    .headers(headersMap)
222                    .responseFormat(ResponseFormat.JSON)
223                    .auth(this.auth)
224                    .networkSession(this.networkSession)
225                    .build());
226    return JsonManager.deserialize(response.getData(), UploadSession.class);
227  }
228
229  /**
230   * Using this method with urls provided in response when creating a new upload session is
231   * preferred to use over UploadFilePart method. This allows to always upload your content to the
232   * closest Box data center and can significantly improve upload speed. Uploads a chunk of a file
233   * for an upload session.
234   *
235   * <p>The actual endpoint URL is returned by the [`Create upload
236   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
237   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
238   *
239   * @param url URL of uploadFilePart method
240   * @param requestBody Request body of uploadFilePart method
241   * @param headers Headers of uploadFilePart method
242   */
243  public UploadedPart uploadFilePartByUrl(
244      String url, InputStream requestBody, UploadFilePartByUrlHeaders headers) {
245    Map<String, String> headersMap =
246        prepareParams(
247            mergeMaps(
248                mapOf(
249                    entryOf("digest", convertToString(headers.getDigest())),
250                    entryOf("content-range", convertToString(headers.getContentRange()))),
251                headers.getExtraHeaders()));
252    FetchResponse response =
253        this.networkSession
254            .getNetworkClient()
255            .fetch(
256                new FetchOptions.Builder(url, "PUT")
257                    .headers(headersMap)
258                    .fileStream(requestBody)
259                    .contentType("application/octet-stream")
260                    .responseFormat(ResponseFormat.JSON)
261                    .auth(this.auth)
262                    .networkSession(this.networkSession)
263                    .build());
264    return JsonManager.deserialize(response.getData(), UploadedPart.class);
265  }
266
267  /**
268   * Uploads a chunk of a file for an upload session.
269   *
270   * <p>The actual endpoint URL is returned by the [`Create upload
271   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
272   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
273   *
274   * @param uploadSessionId The ID of the upload session. Example: "D5E3F7A"
275   * @param requestBody Request body of uploadFilePart method
276   * @param headers Headers of uploadFilePart method
277   */
278  public UploadedPart uploadFilePart(
279      String uploadSessionId, InputStream requestBody, UploadFilePartHeaders headers) {
280    Map<String, String> headersMap =
281        prepareParams(
282            mergeMaps(
283                mapOf(
284                    entryOf("digest", convertToString(headers.getDigest())),
285                    entryOf("content-range", convertToString(headers.getContentRange()))),
286                headers.getExtraHeaders()));
287    FetchResponse response =
288        this.networkSession
289            .getNetworkClient()
290            .fetch(
291                new FetchOptions.Builder(
292                        String.join(
293                            "",
294                            this.networkSession.getBaseUrls().getUploadUrl(),
295                            "/2.0/files/upload_sessions/",
296                            convertToString(uploadSessionId)),
297                        "PUT")
298                    .headers(headersMap)
299                    .fileStream(requestBody)
300                    .contentType("application/octet-stream")
301                    .responseFormat(ResponseFormat.JSON)
302                    .auth(this.auth)
303                    .networkSession(this.networkSession)
304                    .build());
305    return JsonManager.deserialize(response.getData(), UploadedPart.class);
306  }
307
308  /**
309   * Using this method with urls provided in response when creating a new upload session is
310   * preferred to use over DeleteFileUploadSessionById method. This allows to always upload your
311   * content to the closest Box data center and can significantly improve upload speed. Abort an
312   * upload session and discard all data uploaded.
313   *
314   * <p>This cannot be reversed.
315   *
316   * <p>The actual endpoint URL is returned by the [`Create upload
317   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
318   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
319   *
320   * @param url URL of deleteFileUploadSessionById method
321   */
322  public void deleteFileUploadSessionByUrl(String url) {
323    deleteFileUploadSessionByUrl(url, new DeleteFileUploadSessionByUrlHeaders());
324  }
325
326  /**
327   * Using this method with urls provided in response when creating a new upload session is
328   * preferred to use over DeleteFileUploadSessionById method. This allows to always upload your
329   * content to the closest Box data center and can significantly improve upload speed. Abort an
330   * upload session and discard all data uploaded.
331   *
332   * <p>This cannot be reversed.
333   *
334   * <p>The actual endpoint URL is returned by the [`Create upload
335   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
336   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
337   *
338   * @param url URL of deleteFileUploadSessionById method
339   * @param headers Headers of deleteFileUploadSessionById method
340   */
341  public void deleteFileUploadSessionByUrl(
342      String url, DeleteFileUploadSessionByUrlHeaders headers) {
343    Map<String, String> headersMap = prepareParams(mergeMaps(mapOf(), headers.getExtraHeaders()));
344    FetchResponse response =
345        this.networkSession
346            .getNetworkClient()
347            .fetch(
348                new FetchOptions.Builder(url, "DELETE")
349                    .headers(headersMap)
350                    .responseFormat(ResponseFormat.NO_CONTENT)
351                    .auth(this.auth)
352                    .networkSession(this.networkSession)
353                    .build());
354  }
355
356  /**
357   * Abort an upload session and discard all data uploaded.
358   *
359   * <p>This cannot be reversed.
360   *
361   * <p>The actual endpoint URL is returned by the [`Create upload
362   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
363   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
364   *
365   * @param uploadSessionId The ID of the upload session. Example: "D5E3F7A"
366   */
367  public void deleteFileUploadSessionById(String uploadSessionId) {
368    deleteFileUploadSessionById(uploadSessionId, new DeleteFileUploadSessionByIdHeaders());
369  }
370
371  /**
372   * Abort an upload session and discard all data uploaded.
373   *
374   * <p>This cannot be reversed.
375   *
376   * <p>The actual endpoint URL is returned by the [`Create upload
377   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
378   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
379   *
380   * @param uploadSessionId The ID of the upload session. Example: "D5E3F7A"
381   * @param headers Headers of deleteFileUploadSessionById method
382   */
383  public void deleteFileUploadSessionById(
384      String uploadSessionId, DeleteFileUploadSessionByIdHeaders headers) {
385    Map<String, String> headersMap = prepareParams(mergeMaps(mapOf(), headers.getExtraHeaders()));
386    FetchResponse response =
387        this.networkSession
388            .getNetworkClient()
389            .fetch(
390                new FetchOptions.Builder(
391                        String.join(
392                            "",
393                            this.networkSession.getBaseUrls().getUploadUrl(),
394                            "/2.0/files/upload_sessions/",
395                            convertToString(uploadSessionId)),
396                        "DELETE")
397                    .headers(headersMap)
398                    .responseFormat(ResponseFormat.NO_CONTENT)
399                    .auth(this.auth)
400                    .networkSession(this.networkSession)
401                    .build());
402  }
403
404  /**
405   * Using this method with urls provided in response when creating a new upload session is
406   * preferred to use over GetFileUploadSessionParts method. This allows to always upload your
407   * content to the closest Box data center and can significantly improve upload speed. Return a
408   * list of the chunks uploaded to the upload session so far.
409   *
410   * <p>The actual endpoint URL is returned by the [`Create upload
411   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
412   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
413   *
414   * @param url URL of getFileUploadSessionParts method
415   */
416  public UploadParts getFileUploadSessionPartsByUrl(String url) {
417    return getFileUploadSessionPartsByUrl(
418        url,
419        new GetFileUploadSessionPartsByUrlQueryParams(),
420        new GetFileUploadSessionPartsByUrlHeaders());
421  }
422
423  /**
424   * Using this method with urls provided in response when creating a new upload session is
425   * preferred to use over GetFileUploadSessionParts method. This allows to always upload your
426   * content to the closest Box data center and can significantly improve upload speed. Return a
427   * list of the chunks uploaded to the upload session so far.
428   *
429   * <p>The actual endpoint URL is returned by the [`Create upload
430   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
431   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
432   *
433   * @param url URL of getFileUploadSessionParts method
434   * @param queryParams Query parameters of getFileUploadSessionParts method
435   */
436  public UploadParts getFileUploadSessionPartsByUrl(
437      String url, GetFileUploadSessionPartsByUrlQueryParams queryParams) {
438    return getFileUploadSessionPartsByUrl(
439        url, queryParams, new GetFileUploadSessionPartsByUrlHeaders());
440  }
441
442  /**
443   * Using this method with urls provided in response when creating a new upload session is
444   * preferred to use over GetFileUploadSessionParts method. This allows to always upload your
445   * content to the closest Box data center and can significantly improve upload speed. Return a
446   * list of the chunks uploaded to the upload session so far.
447   *
448   * <p>The actual endpoint URL is returned by the [`Create upload
449   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
450   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
451   *
452   * @param url URL of getFileUploadSessionParts method
453   * @param headers Headers of getFileUploadSessionParts method
454   */
455  public UploadParts getFileUploadSessionPartsByUrl(
456      String url, GetFileUploadSessionPartsByUrlHeaders headers) {
457    return getFileUploadSessionPartsByUrl(
458        url, new GetFileUploadSessionPartsByUrlQueryParams(), headers);
459  }
460
461  /**
462   * Using this method with urls provided in response when creating a new upload session is
463   * preferred to use over GetFileUploadSessionParts method. This allows to always upload your
464   * content to the closest Box data center and can significantly improve upload speed. Return a
465   * list of the chunks uploaded to the upload session so far.
466   *
467   * <p>The actual endpoint URL is returned by the [`Create upload
468   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
469   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
470   *
471   * @param url URL of getFileUploadSessionParts method
472   * @param queryParams Query parameters of getFileUploadSessionParts method
473   * @param headers Headers of getFileUploadSessionParts method
474   */
475  public UploadParts getFileUploadSessionPartsByUrl(
476      String url,
477      GetFileUploadSessionPartsByUrlQueryParams queryParams,
478      GetFileUploadSessionPartsByUrlHeaders headers) {
479    Map<String, String> queryParamsMap =
480        prepareParams(
481            mapOf(
482                entryOf("offset", convertToString(queryParams.getOffset())),
483                entryOf("limit", convertToString(queryParams.getLimit()))));
484    Map<String, String> headersMap = prepareParams(mergeMaps(mapOf(), headers.getExtraHeaders()));
485    FetchResponse response =
486        this.networkSession
487            .getNetworkClient()
488            .fetch(
489                new FetchOptions.Builder(url, "GET")
490                    .params(queryParamsMap)
491                    .headers(headersMap)
492                    .responseFormat(ResponseFormat.JSON)
493                    .auth(this.auth)
494                    .networkSession(this.networkSession)
495                    .build());
496    return JsonManager.deserialize(response.getData(), UploadParts.class);
497  }
498
499  /**
500   * Return a list of the chunks uploaded to the upload session so far.
501   *
502   * <p>The actual endpoint URL is returned by the [`Create upload
503   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
504   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
505   *
506   * @param uploadSessionId The ID of the upload session. Example: "D5E3F7A"
507   */
508  public UploadParts getFileUploadSessionParts(String uploadSessionId) {
509    return getFileUploadSessionParts(
510        uploadSessionId,
511        new GetFileUploadSessionPartsQueryParams(),
512        new GetFileUploadSessionPartsHeaders());
513  }
514
515  /**
516   * Return a list of the chunks uploaded to the upload session so far.
517   *
518   * <p>The actual endpoint URL is returned by the [`Create upload
519   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
520   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
521   *
522   * @param uploadSessionId The ID of the upload session. Example: "D5E3F7A"
523   * @param queryParams Query parameters of getFileUploadSessionParts method
524   */
525  public UploadParts getFileUploadSessionParts(
526      String uploadSessionId, GetFileUploadSessionPartsQueryParams queryParams) {
527    return getFileUploadSessionParts(
528        uploadSessionId, queryParams, new GetFileUploadSessionPartsHeaders());
529  }
530
531  /**
532   * Return a list of the chunks uploaded to the upload session so far.
533   *
534   * <p>The actual endpoint URL is returned by the [`Create upload
535   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
536   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
537   *
538   * @param uploadSessionId The ID of the upload session. Example: "D5E3F7A"
539   * @param headers Headers of getFileUploadSessionParts method
540   */
541  public UploadParts getFileUploadSessionParts(
542      String uploadSessionId, GetFileUploadSessionPartsHeaders headers) {
543    return getFileUploadSessionParts(
544        uploadSessionId, new GetFileUploadSessionPartsQueryParams(), headers);
545  }
546
547  /**
548   * Return a list of the chunks uploaded to the upload session so far.
549   *
550   * <p>The actual endpoint URL is returned by the [`Create upload
551   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
552   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
553   *
554   * @param uploadSessionId The ID of the upload session. Example: "D5E3F7A"
555   * @param queryParams Query parameters of getFileUploadSessionParts method
556   * @param headers Headers of getFileUploadSessionParts method
557   */
558  public UploadParts getFileUploadSessionParts(
559      String uploadSessionId,
560      GetFileUploadSessionPartsQueryParams queryParams,
561      GetFileUploadSessionPartsHeaders headers) {
562    Map<String, String> queryParamsMap =
563        prepareParams(
564            mapOf(
565                entryOf("offset", convertToString(queryParams.getOffset())),
566                entryOf("limit", convertToString(queryParams.getLimit()))));
567    Map<String, String> headersMap = prepareParams(mergeMaps(mapOf(), headers.getExtraHeaders()));
568    FetchResponse response =
569        this.networkSession
570            .getNetworkClient()
571            .fetch(
572                new FetchOptions.Builder(
573                        String.join(
574                            "",
575                            this.networkSession.getBaseUrls().getUploadUrl(),
576                            "/2.0/files/upload_sessions/",
577                            convertToString(uploadSessionId),
578                            "/parts"),
579                        "GET")
580                    .params(queryParamsMap)
581                    .headers(headersMap)
582                    .responseFormat(ResponseFormat.JSON)
583                    .auth(this.auth)
584                    .networkSession(this.networkSession)
585                    .build());
586    return JsonManager.deserialize(response.getData(), UploadParts.class);
587  }
588
589  /**
590   * Using this method with urls provided in response when creating a new upload session is
591   * preferred to use over CreateFileUploadSessionCommit method. This allows to always upload your
592   * content to the closest Box data center and can significantly improve upload speed. Close an
593   * upload session and create a file from the uploaded chunks.
594   *
595   * <p>The actual endpoint URL is returned by the [`Create upload
596   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
597   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
598   *
599   * @param url URL of createFileUploadSessionCommit method
600   * @param requestBody Request body of createFileUploadSessionCommit method
601   * @param headers Headers of createFileUploadSessionCommit method
602   */
603  public Files createFileUploadSessionCommitByUrl(
604      String url,
605      CreateFileUploadSessionCommitByUrlRequestBody requestBody,
606      CreateFileUploadSessionCommitByUrlHeaders headers) {
607    Map<String, String> headersMap =
608        prepareParams(
609            mergeMaps(
610                mapOf(
611                    entryOf("digest", convertToString(headers.getDigest())),
612                    entryOf("if-match", convertToString(headers.getIfMatch())),
613                    entryOf("if-none-match", convertToString(headers.getIfNoneMatch()))),
614                headers.getExtraHeaders()));
615    FetchResponse response =
616        this.networkSession
617            .getNetworkClient()
618            .fetch(
619                new FetchOptions.Builder(url, "POST")
620                    .headers(headersMap)
621                    .data(JsonManager.serialize(requestBody))
622                    .contentType("application/json")
623                    .responseFormat(ResponseFormat.JSON)
624                    .auth(this.auth)
625                    .networkSession(this.networkSession)
626                    .build());
627    if (convertToString(response.getStatus()).equals("202")) {
628      return null;
629    }
630    return JsonManager.deserialize(response.getData(), Files.class);
631  }
632
633  /**
634   * Close an upload session and create a file from the uploaded chunks.
635   *
636   * <p>The actual endpoint URL is returned by the [`Create upload
637   * session`](https://developer.box.com/reference/post-files-upload-sessions) and [`Get upload
638   * session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints.
639   *
640   * @param uploadSessionId The ID of the upload session. Example: "D5E3F7A"
641   * @param requestBody Request body of createFileUploadSessionCommit method
642   * @param headers Headers of createFileUploadSessionCommit method
643   */
644  public Files createFileUploadSessionCommit(
645      String uploadSessionId,
646      CreateFileUploadSessionCommitRequestBody requestBody,
647      CreateFileUploadSessionCommitHeaders headers) {
648    Map<String, String> headersMap =
649        prepareParams(
650            mergeMaps(
651                mapOf(
652                    entryOf("digest", convertToString(headers.getDigest())),
653                    entryOf("if-match", convertToString(headers.getIfMatch())),
654                    entryOf("if-none-match", convertToString(headers.getIfNoneMatch()))),
655                headers.getExtraHeaders()));
656    FetchResponse response =
657        this.networkSession
658            .getNetworkClient()
659            .fetch(
660                new FetchOptions.Builder(
661                        String.join(
662                            "",
663                            this.networkSession.getBaseUrls().getUploadUrl(),
664                            "/2.0/files/upload_sessions/",
665                            convertToString(uploadSessionId),
666                            "/commit"),
667                        "POST")
668                    .headers(headersMap)
669                    .data(JsonManager.serialize(requestBody))
670                    .contentType("application/json")
671                    .responseFormat(ResponseFormat.JSON)
672                    .auth(this.auth)
673                    .networkSession(this.networkSession)
674                    .build());
675    if (convertToString(response.getStatus()).equals("202")) {
676      return null;
677    }
678    return JsonManager.deserialize(response.getData(), Files.class);
679  }
680
681  public PartAccumulator reducer(PartAccumulator acc, InputStream chunk) {
682    long lastIndex = acc.getLastIndex();
683    List<UploadPart> parts = acc.getParts();
684    byte[] chunkBuffer = readByteStream(chunk);
685    Hash hash = new Hash(HashName.SHA1);
686    hash.updateHash(chunkBuffer);
687    String sha1 = hash.digestHash("base64");
688    String digest = String.join("", "sha=", sha1);
689    int chunkSize = bufferLength(chunkBuffer);
690    long bytesStart = lastIndex + 1;
691    long bytesEnd = lastIndex + chunkSize;
692    String contentRange =
693        String.join(
694            "",
695            "bytes ",
696            convertToString(bytesStart),
697            "-",
698            convertToString(bytesEnd),
699            "/",
700            convertToString(acc.getFileSize()));
701    UploadedPart uploadedPart =
702        this.uploadFilePartByUrl(
703            acc.getUploadPartUrl(),
704            generateByteStreamFromBuffer(chunkBuffer),
705            new UploadFilePartByUrlHeaders(digest, contentRange));
706    UploadPart part = uploadedPart.getPart();
707    String partSha1 = hexToBase64(part.getSha1());
708    assert partSha1.equals(sha1);
709    assert part.getSize() == chunkSize;
710    assert part.getOffset() == bytesStart;
711    acc.getFileHash().updateHash(chunkBuffer);
712    return new PartAccumulator(
713        bytesEnd,
714        Stream.concat(parts.stream(), Arrays.asList(part).stream()).collect(Collectors.toList()),
715        acc.getFileSize(),
716        acc.getUploadPartUrl(),
717        acc.getFileHash());
718  }
719
720  /**
721   * Starts the process of chunk uploading a big file. Should return a File object representing
722   * uploaded file.
723   *
724   * @param file The stream of the file to upload.
725   * @param fileName The name of the file, which will be used for storage in Box.
726   * @param fileSize The total size of the file for the chunked upload in bytes.
727   * @param parentFolderId The ID of the folder where the file should be uploaded.
728   */
729  public FileFull uploadBigFile(
730      InputStream file, String fileName, long fileSize, String parentFolderId) {
731    UploadSession uploadSession =
732        this.createFileUploadSession(
733            new CreateFileUploadSessionRequestBody(parentFolderId, fileSize, fileName));
734    String uploadPartUrl = uploadSession.getSessionEndpoints().getUploadPart();
735    String commitUrl = uploadSession.getSessionEndpoints().getCommit();
736    String listPartsUrl = uploadSession.getSessionEndpoints().getListParts();
737    long partSize = uploadSession.getPartSize();
738    int totalParts = uploadSession.getTotalParts();
739    assert partSize * totalParts >= fileSize;
740    assert uploadSession.getNumPartsProcessed() == 0;
741    Hash fileHash = new Hash(HashName.SHA1);
742    Iterator<InputStream> chunksIterator = iterateChunks(file, partSize, fileSize);
743    PartAccumulator results =
744        reduceIterator(
745            chunksIterator,
746            this::reducer,
747            new PartAccumulator(-1, Collections.emptyList(), fileSize, uploadPartUrl, fileHash));
748    List<UploadPart> parts = results.getParts();
749    UploadParts processedSessionParts = this.getFileUploadSessionPartsByUrl(listPartsUrl);
750    assert processedSessionParts.getTotalCount() == totalParts;
751    String sha1 = fileHash.digestHash("base64");
752    String digest = String.join("", "sha=", sha1);
753    Files committedSession =
754        this.createFileUploadSessionCommitByUrl(
755            commitUrl,
756            new CreateFileUploadSessionCommitByUrlRequestBody(parts),
757            new CreateFileUploadSessionCommitByUrlHeaders(digest));
758    return committedSession.getEntries().get(0);
759  }
760
761  public Authentication getAuth() {
762    return auth;
763  }
764
765  public NetworkSession getNetworkSession() {
766    return networkSession;
767  }
768
769  public static class Builder {
770
771    protected Authentication auth;
772
773    protected NetworkSession networkSession;
774
775    public Builder() {}
776
777    public Builder auth(Authentication auth) {
778      this.auth = auth;
779      return this;
780    }
781
782    public Builder networkSession(NetworkSession networkSession) {
783      this.networkSession = networkSession;
784      return this;
785    }
786
787    public ChunkedUploadsManager build() {
788      if (this.networkSession == null) {
789        this.networkSession = new NetworkSession();
790      }
791      return new ChunkedUploadsManager(this);
792    }
793  }
794}