2525import java .io .RandomAccessFile ;
2626import java .util .Arrays ;
2727import java .util .Iterator ;
28- import java .util .List ;
2928import java .util .concurrent .TimeUnit ;
3029
3130import static ratismal .drivebackup .config .Localization .intl ;
@@ -40,7 +39,6 @@ public class OneDriveUploader extends Uploader {
4039
4140 private final UploadLogger logger ;
4241
43- private long totalUploaded ;
4442 private String accessToken = "" ;
4543 private String refreshToken ;
4644
@@ -50,16 +48,8 @@ public class OneDriveUploader extends Uploader {
5048 private static final MediaType jsonMediaType = MediaType .parse ("application/json; charset=utf-8" );
5149 private static final MediaType textMediaType = MediaType .parse ("text/plain" );
5250
53- /**
54- * Size of the file chunks to upload to OneDrive
55- */
56- private static final int CHUNK_SIZE = 5 * 1024 * 1024 ;
51+ private static final int UPLOAD_CHUNK_SIZE = 5 * 1024 * 1024 ;
5752
58- /**
59- * File upload buffer
60- */
61- private RandomAccessFile raf ;
62-
6353 /**
6454 * Creates an instance of the {@code OneDriveUploader} object
6555 */
@@ -68,7 +58,6 @@ public OneDriveUploader(UploadLogger logger) {
6858 this .logger = logger ;
6959 setAuthProvider (AuthenticationProvider .ONEDRIVE );
7060 try {
71- setRanges (new String [0 ]);
7261 refreshToken = Authenticator .getRefreshToken (AuthenticationProvider .ONEDRIVE );
7362 retrieveNewAccessToken ();
7463 } catch (Exception e ) {
@@ -105,6 +94,7 @@ private void retrieveNewAccessToken() throws Exception {
10594 accessToken = parsedResponse .getString ("access_token" );
10695 }
10796 }
97+
10898 @ Override
10999 public boolean isAuthenticated () {
110100 return !accessToken .isEmpty ();
@@ -135,29 +125,26 @@ public void test(File testFile) {
135125 * @param location of the file (ex. plugins, world)
136126 */
137127 @ Override
138- public void uploadFile (File file , String location ) throws IOException {
128+ public void uploadFile (File file , String location ) {
139129 try {
140- resetRanges ();
141130 String destinationRoot = normalizePath (ConfigParser .getConfig ().backupStorage .remoteDirectory );
142131 String destinationPath = concatPath (destinationRoot , location );
143132 FQID destinationId = createPath (destinationPath );
144133 String uploadURL = createUploadSession (file .getName (), destinationId );
145- raf = new RandomAccessFile (file , "r" );
146- uploadToSession (uploadURL , file .length ());
147- try {
148- pruneBackups (destinationId );
149- } catch (Exception e ) {
150- logger .log (intl ("backup-method-prune-failed" ));
151- throw e ;
134+ try (RandomAccessFile raf = new RandomAccessFile (file , "r" )) {
135+ uploadToSession (uploadURL , raf );
136+ try {
137+ pruneBackups (destinationId );
138+ } catch (Exception e ) {
139+ logger .log (intl ("backup-method-prune-failed" ));
140+ throw e ;
141+ }
152142 }
153143 } catch (Exception exception ) {
154144 NetUtil .catchException (exception , "graph.microsoft.com" , logger );
155145 MessageUtil .sendConsoleException (exception );
156146 setErrorOccurred (true );
157147 }
158- if (raf != null ) {
159- raf .close ();
160- }
161148 }
162149
163150 /**
@@ -453,27 +440,32 @@ private String createUploadSession(@NotNull String fileName, @NotNull FQID desti
453440 * uploads the file to a session with the given upload URL. some errors are handled via automatic retries.
454441 *
455442 * @param uploadURL of the upload session
456- * @param fileSize of the file to upload
457- * @throws IOException if a request could not be executed
443+ * @param randomAccessFile to upload
444+ * @throws IOException if a request could not be executed, or randomAccessFile could not be read
458445 * @throws GraphApiErrorException with the last error after max retries
459446 * @throws InterruptedException if interrupted during retries
447+ * @throws JSONException if the responses do not have the expected values
448+ * @throws NumberFormatException if the responses do not have the expected values
449+ * @throws IndexOutOfBoundsException if the responses do not have the expected values
460450 */
461- private void uploadToSession (@ NotNull String uploadURL , long fileSize )
451+ private void uploadToSession (@ NotNull String uploadURL , @ NotNull RandomAccessFile randomAccessFile )
462452 throws IOException , GraphApiErrorException , InterruptedException {
463453 int exponentialBackoffMillis = EXPONENTIAL_BACKOFF_MILLIS_DEFAULT ;
464454 int retryCount = 0 ;
455+ Range range = new Range (0 , UPLOAD_CHUNK_SIZE );
465456 while (true ) {
466- byte [] bytesToUpload = getChunk ();
457+ byte [] bytesToUpload = getChunk (randomAccessFile , range );
467458 Request uploadRequest = new Request .Builder ()
468- .addHeader ("Content-Range" , String .format ("bytes %d-%d/%d" , getTotalUploaded (), getTotalUploaded () + bytesToUpload .length - 1 , fileSize ))
459+ .addHeader ("Content-Range" , String .format ("bytes %d-%d/%d" ,
460+ range .start , range .start + bytesToUpload .length - 1 , randomAccessFile .length ()))
469461 .url (uploadURL )
470462 .put (RequestBody .create (bytesToUpload , zipMediaType ))
471463 .build ();
472464 try (Response uploadResponse = DriveBackup .httpClient .newCall (uploadRequest ).execute ()) {
473465 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 ]) );
466+ JSONObject responseObject = new JSONObject (uploadResponse .body ().string ());
467+ JSONArray expectedRanges = responseObject .getJSONArray ("nextExpectedRanges" );
468+ range = new Range ( expectedRanges . getString ( 0 ), UPLOAD_CHUNK_SIZE );
477469 exponentialBackoffMillis = EXPONENTIAL_BACKOFF_MILLIS_DEFAULT ;
478470 retryCount = 0 ;
479471 } else if (uploadResponse .code () == 201 || uploadResponse .code () == 200 ) {
@@ -540,67 +532,52 @@ private void pruneBackups(@NotNull FQID parent) throws IOException, GraphApiErro
540532 */
541533 private static class Range {
542534 private final long start ;
543- private final long end ;
535+ private final int length ;
544536
545537 /**
546538 * Creates an instance of the {@code Range} object
547539 * @param start the index of the first byte
548- * @param end the index of the last byte
540+ * @param length of the range
549541 */
550- private Range (long start , long end ) {
542+ public Range (long start , int length ) {
551543 this .start = start ;
552- this .end = end ;
544+ this .length = length ;
553545 }
554- }
555546
556- /**
557- * Resets the number of bytes uploaded in the last chunk, and the number of bytes uploaded in total.
558- */
559- private void resetRanges () {
560- totalUploaded = 0 ;
561- }
562-
563- /**
564- * Sets the number of bytes uploaded in the last chunk,
565- * and the number of bytes uploaded in total from the ranges of bytes the OneDrive API requested to be uploaded last.
566- * @param stringRanges the ranges of bytes requested
567- */
568- private void setRanges (@ NotNull String [] stringRanges ) {
569- Range [] ranges = new Range [stringRanges .length ];
570- for (int i = 0 ; i < stringRanges .length ; i ++) {
571- long start = Long .parseLong (stringRanges [i ].substring (0 , stringRanges [i ].indexOf ('-' )));
572- String s = stringRanges [i ].substring (stringRanges [i ].indexOf ('-' ) + 1 );
573- long end = 0 ;
574- if (!s .isEmpty ()) {
575- end = Long .parseLong (s );
547+ /**
548+ * Creates an instance of the {@code Range} object
549+ * @param range in the format of {@code 000-000 or 000-}
550+ * @param maxLength to clamp the range to
551+ * @throws NumberFormatException if parseLong fails on range
552+ * @throws IndexOutOfBoundsException if range has no {@code -}
553+ */
554+ public Range (@ NotNull String range , int maxLength ) {
555+ int dash = range .indexOf ('-' );
556+ this .start = Long .parseLong (range .substring (0 , dash ));
557+ String rhs = range .substring (dash + 1 );
558+ long end = Long .MAX_VALUE ;
559+ if (!rhs .isEmpty ()) {
560+ end = Long .parseLong (rhs );
576561 }
577- ranges [i ] = new Range (start , end );
578- }
579- if (ranges .length > 0 ) {
580- totalUploaded = ranges [0 ].start ;
562+ this .length = (int )Math .min ((end - start ) + 1 , maxLength );
581563 }
582564 }
583565
584566 /**
585- * Gets an array of bytes to upload next from the file buffer based on the number of bytes uploaded so far.
586- * @return the array of bytes
567+ * gets an array of bytes to upload next from the file buffer
568+ * @param raf file to get chunk from
569+ * @param range in file to get chunk from
570+ * @return the array of bytes; may be smaller than range if {@code raf.length() - range.start < range.length}
587571 * @throws IOException on file read errors
588572 */
589- private byte @ NotNull [] getChunk () throws IOException {
590- byte [] bytes = new byte [CHUNK_SIZE ];
591- raf .seek (totalUploaded );
592- int read = raf .read (bytes );
593- if (read < CHUNK_SIZE ) {
594- bytes = Arrays .copyOf (bytes , read );
595- }
573+ private static byte @ NotNull [] getChunk (RandomAccessFile raf , Range range ) throws IOException {
574+ if (range .start >= raf .length ()) {
575+ return new byte [0 ];
576+ }
577+ int chunkSize = (int )Math .min (range .length , raf .length () - range .start );
578+ byte [] bytes = new byte [chunkSize ];
579+ raf .seek (range .start );
580+ raf .read (bytes );
596581 return bytes ;
597582 }
598-
599- /**
600- * Gets the number of bytes uploaded in total
601- * @return the number of bytes
602- */
603- private long getTotalUploaded () {
604- return totalUploaded ;
605- }
606583}
0 commit comments