Skip to content

Commit 4688e6c

Browse files
refactor upload range logic
1 parent 91d054d commit 4688e6c

File tree

1 file changed

+55
-78
lines changed

1 file changed

+55
-78
lines changed

DriveBackup/src/main/java/ratismal/drivebackup/uploaders/onedrive/OneDriveUploader.java

Lines changed: 55 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import java.io.RandomAccessFile;
2626
import java.util.Arrays;
2727
import java.util.Iterator;
28-
import java.util.List;
2928
import java.util.concurrent.TimeUnit;
3029

3130
import 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

Comments
 (0)