@@ -141,50 +141,9 @@ public void uploadFile(File file, String location) throws IOException {
141141 String destinationRoot = normalizePath (ConfigParser .getConfig ().backupStorage .remoteDirectory );
142142 String destinationPath = concatPath (destinationRoot , location );
143143 FQID destinationId = createPath (destinationPath );
144- Request request = new Request .Builder ()
145- .addHeader ("Authorization" , "Bearer " + accessToken )
146- .url ("https://graph.microsoft.com/v1.0/drives/" + destinationId .driveId
147- + "/items/" + destinationId .itemId + ":/" + file .getName () + ":/createUploadSession" )
148- .post (RequestBody .create ("{}" , jsonMediaType ))
149- .build ();
150- JSONObject parsedResponse ;
151- try (Response response = DriveBackup .httpClient .newCall (request ).execute ()) {
152- parsedResponse = new JSONObject (response .body ().string ());
153- }
154- String uploadURL = parsedResponse .getString ("uploadUrl" );
144+ String uploadURL = createUploadSession (file .getName (), destinationId );
155145 raf = new RandomAccessFile (file , "r" );
156- int exponentialBackoffMillis = EXPONENTIAL_BACKOFF_MILLIS_DEFAULT ;
157- int retryCount = 0 ;
158- while (true ) {
159- byte [] bytesToUpload = getChunk ();
160- request = new Request .Builder ()
161- .addHeader ("Content-Range" , String .format ("bytes %d-%d/%d" , getTotalUploaded (), getTotalUploaded () + bytesToUpload .length - 1 , file .length ()))
162- .url (uploadURL )
163- .put (RequestBody .create (bytesToUpload , zipMediaType ))
164- .build ();
165- try (Response uploadResponse = DriveBackup .httpClient .newCall (request ).execute ()) {
166- if (uploadResponse .code () == 202 ) {
167- parsedResponse = new JSONObject (uploadResponse .body ().string ());
168- List <Object > nextExpectedRanges = parsedResponse .getJSONArray ("nextExpectedRanges" ).toList ();
169- setRanges (nextExpectedRanges .toArray (new String [0 ]));
170- exponentialBackoffMillis = EXPONENTIAL_BACKOFF_MILLIS_DEFAULT ;
171- retryCount = 0 ;
172- } else if (uploadResponse .code () == 201 || uploadResponse .code () == 200 ) {
173- break ;
174- } else { // TODO conflict after successful upload not handled
175- if (retryCount > MAX_RETRY_ATTEMPTS ) {
176- request = new Request .Builder ().url (uploadURL ).delete ().build ();
177- DriveBackup .httpClient .newCall (request ).execute ().close ();
178- throw new IOException (String .format ("Upload failed after %d retries. %d %s" , MAX_RETRY_ATTEMPTS , uploadResponse .code (), uploadResponse .message ()));
179- }
180- if (uploadResponse .code () >= 500 && uploadResponse .code () < 600 ) {
181- Thread .sleep (exponentialBackoffMillis );
182- exponentialBackoffMillis *= EXPONENTIAL_BACKOFF_FACTOR ;
183- }
184- retryCount ++;
185- }
186- }
187- }
146+ uploadToSession (uploadURL , file .length ());
188147 try {
189148 pruneBackups (destinationId );
190149 } catch (Exception e ) {
@@ -464,6 +423,77 @@ private FQID uploadSmallFile(@NotNull File file, @NotNull FQID destinationFolder
464423 }
465424 }
466425
426+ /**
427+ * creates an upload session for a file in a destination folder on OneDrive.
428+ *
429+ * @param fileName of the file to upload
430+ * @param destinationFolder as FQID
431+ * @return String with the upload URL for the file
432+ * @throws IOException if there is an error executing the request
433+ * @throws GraphApiErrorException if the upload session was not created
434+ * @throws JSONException if the response does not contain the expected values
435+ */
436+ @ NotNull
437+ private String createUploadSession (@ NotNull String fileName , @ NotNull FQID destinationFolder ) throws IOException , GraphApiErrorException {
438+ Request request = new Request .Builder ()
439+ .addHeader ("Authorization" , "Bearer " + accessToken )
440+ .url ("https://graph.microsoft.com/v1.0/drives/" + destinationFolder .driveId
441+ + "/items/" + destinationFolder .itemId + ":/" + fileName + ":/createUploadSession" )
442+ .post (RequestBody .create ("{}" , jsonMediaType ))
443+ .build ();
444+ try (Response response = DriveBackup .httpClient .newCall (request ).execute ()) {
445+ if (!response .isSuccessful ()) {
446+ throw new GraphApiErrorException (response );
447+ }
448+ return new JSONObject (response .body ().string ()).getString ("uploadUrl" );
449+ }
450+ }
451+
452+ /**
453+ * uploads the file to a session with the given upload URL. some errors are handled via automatic retries.
454+ *
455+ * @param uploadURL of the upload session
456+ * @param fileSize of the file to upload
457+ * @throws IOException if a request could not be executed
458+ * @throws GraphApiErrorException with the last error after max retries
459+ * @throws InterruptedException if interrupted during retries
460+ */
461+ private void uploadToSession (@ NotNull String uploadURL , long fileSize )
462+ throws IOException , GraphApiErrorException , InterruptedException {
463+ int exponentialBackoffMillis = EXPONENTIAL_BACKOFF_MILLIS_DEFAULT ;
464+ int retryCount = 0 ;
465+ while (true ) {
466+ byte [] bytesToUpload = getChunk ();
467+ Request uploadRequest = new Request .Builder ()
468+ .addHeader ("Content-Range" , String .format ("bytes %d-%d/%d" , getTotalUploaded (), getTotalUploaded () + bytesToUpload .length - 1 , fileSize ))
469+ .url (uploadURL )
470+ .put (RequestBody .create (bytesToUpload , zipMediaType ))
471+ .build ();
472+ try (Response uploadResponse = DriveBackup .httpClient .newCall (uploadRequest ).execute ()) {
473+ if (uploadResponse .code () == 202 ) {
474+ JSONObject parsedResponse = new JSONObject (uploadResponse .body ().string ());
475+ List <Object > nextExpectedRanges = parsedResponse .getJSONArray ("nextExpectedRanges" ).toList ();
476+ setRanges (nextExpectedRanges .toArray (new String [0 ]));
477+ exponentialBackoffMillis = EXPONENTIAL_BACKOFF_MILLIS_DEFAULT ;
478+ retryCount = 0 ;
479+ } else if (uploadResponse .code () == 201 || uploadResponse .code () == 200 ) {
480+ break ;
481+ } else { // TODO conflict after successful upload not handled
482+ if (retryCount > MAX_RETRY_ATTEMPTS ) {
483+ Request cancelRequest = new Request .Builder ().url (uploadURL ).delete ().build ();
484+ DriveBackup .httpClient .newCall (cancelRequest ).execute ().close ();
485+ throw new GraphApiErrorException (uploadResponse );
486+ }
487+ if (uploadResponse .code () >= 500 && uploadResponse .code () < 600 ) {
488+ TimeUnit .MILLISECONDS .sleep (exponentialBackoffMillis );
489+ exponentialBackoffMillis *= EXPONENTIAL_BACKOFF_FACTOR ;
490+ }
491+ retryCount ++;
492+ }
493+ }
494+ }
495+ }
496+
467497 /**
468498 * Deletes the oldest files in the specified folder past the number to retain from the authenticated user's OneDrive.
469499 * <p>
0 commit comments