package com.tomclaw.mandarin.core;
import android.os.Build;
import android.text.TextUtils;
import com.tomclaw.mandarin.BuildConfig;
import com.tomclaw.mandarin.core.exceptions.ServerInternalException;
import com.tomclaw.mandarin.core.exceptions.UnauthorizedException;
import com.tomclaw.mandarin.core.exceptions.UnknownResponseException;
import com.tomclaw.mandarin.im.AccountRoot;
import com.tomclaw.mandarin.util.AlterableBody;
import com.tomclaw.mandarin.util.Logger;
import com.tomclaw.mandarin.util.VariableBuffer;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.SocketTimeoutException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Response;
import static com.tomclaw.mandarin.util.HttpStatus.SC_OK;
import static com.tomclaw.mandarin.util.HttpStatus.SC_PARTIAL_CONTENT;
/**
* Created by Solkin on 14.10.2014.
*/
public abstract class RangedUploadRequest<A extends AccountRoot> extends Request<A> {
private final transient OkHttpClient httpClient = new OkHttpClient.Builder().build();
private static final String CONTENT_RANGE = "Content-Range";
public RangedUploadRequest() {
}
@Override
public int executeRequest() {
try {
onStarted();
VirtualFile virtualFile = getVirtualFile();
long size = virtualFile.getSize();
long sent = 0;
int cache;
VariableBuffer buffer = new VariableBuffer();
String contentType = virtualFile.getMimeType();
boolean completed = false;
String successReply = null;
// Obtain uploading Url.
String url = getUrl(virtualFile.getName(), virtualFile.getSize());
// Starting upload.
MediaType type = MediaType.parse("application/octet-stream");
AlterableBody body = new AlterableBody(type);
okhttp3.Request request = new okhttp3.Request.Builder()
.url(url)
.addHeader("Connection", "Keep-Alive")
.addHeader("User-Agent", getUserAgent())
.addHeader("Accept-Ranges", "bytes")
.post(body)
.build();
do {
InputStream input = null;
try {
input = virtualFile.openInputStream(getAccountRoot().getContext());
sent = input.skip(sent);
while ((cache = input.read(buffer.calculateBuffer())) != -1 || sent < size) {
checkInterrupted();
// Checking for continuous stream.
if (sent + cache > size) {
// ... and stop at the specified size.
cache = (int) (size - sent);
}
// Setup content range.
String range = "bytes " + sent + "-" + (sent + cache - 1) + "/" + size;
Logger.log("upload range: " + range);
body.setContent(buffer.getBuffer());
body.setOffset(0);
body.setByteCount(cache);
request = request.newBuilder()
.header(CONTENT_RANGE, range)
.build();
checkInterrupted();
buffer.onExecuteStart();
Response response = httpClient.newCall(request).execute();
buffer.onExecuteCompleted(cache);
try {
int responseCode = response.code();
switch (responseCode) {
case SC_OK:
// Uploading completed successfully.
successReply = response.body().string();
completed = true;
break;
case SC_PARTIAL_CONTENT:
// Server is still hungry. Next chunk, please...
break;
default:
// Seems to be error code. Sadly.
identifyErrorResponse(responseCode);
break;
}
} finally {
response.close();
}
sent += cache;
onBufferReleased(sent, size);
checkInterrupted();
}
} catch (FileNotFoundException ex) {
// Where is my file?! :'(
throw ex;
} catch (SocketTimeoutException ex) {
// Pretty network exception.
Logger.log("SocketTimeoutException exception while uploading", ex);
Thread.sleep(3000);
} catch (InterruptedIOException ex) {
// Thread interrupted exception.
Logger.log("Interruption while uploading", ex);
throw ex;
} catch (IOException ex) {
// Pretty network exception.
Logger.log("Io exception while uploading", ex);
Thread.sleep(3000);
} finally {
if (input != null) {
try {
input.close();
} catch (IOException ignored) {
}
}
}
} while (!completed);
if (!TextUtils.isEmpty(successReply)) {
onSuccess(successReply);
}
return REQUEST_DELETE;
} catch (UnauthorizedException ex) {
Logger.log("Unauthorized exception while uploading", ex);
onFail();
return REQUEST_LATER;
} catch (ServerInternalException ex) {
Logger.log("Server internal exception while uploading", ex);
onFail();
return REQUEST_LATER;
} catch (UnknownResponseException ex) {
Logger.log("Unknown response exception while uploading", ex);
onFail();
return REQUEST_LATER;
} catch (SecurityException ex) {
Logger.log("Security exception while uploading", ex);
onFail();
return REQUEST_LATER;
} catch (FileNotFoundException ex) {
Logger.log("File is missing while uploading", ex);
onFileNotFound();
return REQUEST_LATER;
} catch (InterruptedIOException ex) {
Logger.log("Upload interrupted", ex);
onCancel();
return REQUEST_LATER;
} catch (InterruptedException ex) {
Logger.log("Upload interrupted while read", ex);
onCancel();
return REQUEST_LATER;
} catch (Throwable ex) {
Logger.log("Unable to execute upload due to exception", ex);
onPending();
return REQUEST_PENDING;
}
}
private String getUserAgent() {
return "Mandarin/" + BuildConfig.VERSION_NAME + " (Android " + Build.VERSION.RELEASE + ")";
}
private void checkInterrupted() throws InterruptedException {
if (Thread.currentThread().isInterrupted()) {
Logger.log("Upload interrupted by thread iterruption");
throw new InterruptedException();
}
}
protected void identifyErrorResponse(int responseCode) throws Throwable {
Logger.log("uploading error: " + responseCode);
switch (responseCode) {
case 401: {
throw new UnauthorizedException();
}
case 500: {
throw new ServerInternalException();
}
default: {
throw new UnknownResponseException();
}
}
}
protected abstract void onStarted() throws Throwable;
protected abstract void onSuccess(String response) throws Throwable;
protected abstract void onFail();
protected abstract void onCancel();
protected abstract void onFileNotFound();
protected abstract void onPending();
protected abstract void onBufferReleased(long sent, long size);
/**
* Returns uploading virtual file.
*
* @return uploading virtual file
*/
public abstract VirtualFile getVirtualFile();
/**
* Returns request-specific upload Url.
*
* @param name - uploading file name
* @param size - uploading file size
* @return Request-specific Url
* @throws Throwable
*/
protected abstract String getUrl(String name, long size) throws Throwable;
}