Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 165 additions & 54 deletions app/src/main/java/com/odysee/app/tasks/claim/TusPublishTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.os.AsyncTask;
import android.view.View;
import android.widget.ProgressBar;

import com.odysee.app.exceptions.LbryResponseException;
import com.odysee.app.model.Claim;
Expand All @@ -14,13 +15,9 @@

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;

Expand All @@ -29,27 +26,34 @@
import io.tus.java.client.TusExecutor;
import io.tus.java.client.TusUpload;
import io.tus.java.client.TusUploader;
import lombok.Getter;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

// Due to the TusExecutor callback, result is saved in claimResult
// instead of returning through AsyncTask methods.
public class TusPublishTask extends AsyncTask<Void, Void, Void> {
public class TusPublishTask extends AsyncTask<Void, Integer, Void> {
private static final int NOTIFY_RETRY_INTERVAL = 5000;
private static final int STATUS_RETRY_COUNT = 12;
private static final int STATUS_RETRY_INTERVAL = 10000;

private final Claim claim;
private final String filePath;
private final View progressView;
private final String uploadUrl;
private final ProgressBar progressView;
private final String authToken;
private final ClaimResultHandler handler;

private Exception error;
private Claim claimResult;

public TusPublishTask(Claim claim, String filePath, View progressView,
String authToken, ClaimResultHandler handler) {
public TusPublishTask(Claim claim, String filePath, String uploadUrl,
ProgressBar progressView, String authToken, ClaimResultHandler handler) {
this.claim = claim;
this.filePath = filePath;
this.uploadUrl = uploadUrl;
this.progressView = progressView;
this.authToken = authToken;
this.handler = handler;
Expand All @@ -65,9 +69,14 @@ protected void onPreExecute() {

@Override
protected Void doInBackground(Void... voids) {
if (!Helper.isNullOrEmpty(uploadUrl)) {
sendStatusRequest(uploadUrl, STATUS_RETRY_COUNT);
return null;
}

try {
TusClient client = new TusClient();
client.setUploadCreationURL(new URL("https://api.na-backend.odysee.com/api/v2/publish/"));
client.setUploadCreationURL(new URL("https://api.na-backend.odysee.com/api/v3/publish/"));
// TODO: enableResuming
Map<String, String> headers = new HashMap<>();
headers.put("X-Lbry-Auth-Token", authToken);
Expand All @@ -81,52 +90,16 @@ protected Void doInBackground(Void... voids) {
protected void makeAttempt() throws ProtocolException, IOException {
TusUploader uploader = client.createUpload(upload);
uploader.setChunkSize(1024 * 1024);
while (uploader.uploadChunk() > -1);
do {
long total = upload.getSize();
long bytesUploaded = uploader.getOffset();
int progress = (int) ((double) bytesUploaded / total * 100);
publishProgress(progress);
} while (uploader.uploadChunk() > -1);
uploader.finish();

URL notifyURL = new URL(uploader.getUploadURL().toString() + "/notify");
JSONObject requestBody = new JSONObject();
try {
Map<String, Object> options = Helper.buildPublishOptions(claim);

JSONObject params = Lbry.buildJsonParams(options);
long counter = Double.valueOf(System.currentTimeMillis() / 1000.0).longValue();
requestBody.put("jsonrpc", "2.0");
requestBody.put("method", Lbry.METHOD_PUBLISH);
requestBody.put("params", params);
requestBody.put("counter", counter);
} catch (JSONException ex) {
error = ex;
return;
}

RequestBody body = RequestBody.create(requestBody.toString(), Helper.JSON_MEDIA_TYPE);
Request.Builder requestBuilder = new Request.Builder().url(notifyURL).post(body);
requestBuilder.addHeader("X-Lbry-Auth-Token", authToken);
requestBuilder.addHeader("Tus-Resumable", "1.0.0");
Request request = requestBuilder.build();
OkHttpClient client = new OkHttpClient.Builder()
.writeTimeout(300, TimeUnit.SECONDS)
.readTimeout(300, TimeUnit.SECONDS)
.build();

try {
Response response = client.newCall(request).execute();
JSONObject result = (JSONObject) Lbry.parseResponse(response);
if (result.has("outputs")) {
JSONArray outputs = result.getJSONArray("outputs");
for (int i = 0; i < outputs.length(); i++) {
JSONObject output = outputs.getJSONObject(i);
if (output.has("claim_id") && output.has("claim_op")) {
claimResult = Claim.claimFromOutput(output);
break;
}
}
}
} catch (IOException | LbryResponseException | ClassCastException
| JSONException ex) {
error = ex;
}
publishProgress(-1);
makeNotifyRequest(uploader.getUploadURL().toString());
}
};
executor.makeAttempts();
Expand All @@ -137,6 +110,133 @@ protected void makeAttempt() throws ProtocolException, IOException {
return null;
}

@Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];
if (progress >= 0) {
progressView.setIndeterminate(false);
progressView.setProgress(progress);
} else { // -1 = not uploading, so show indeterminate
progressView.setIndeterminate(true);
}
}

void makeNotifyRequest(String uploadUrl) {
URL notifyUrl;
JSONObject requestBody = new JSONObject();
try {
notifyUrl = new URL(uploadUrl + "/notify");

Map<String, Object> options = Helper.buildPublishOptions(claim);
JSONObject params = Lbry.buildJsonParams(options);
long counter = Double.valueOf(System.currentTimeMillis() / 1000.0).longValue();
requestBody.put("jsonrpc", "2.0");
requestBody.put("method", Lbry.METHOD_PUBLISH);
requestBody.put("params", params);
requestBody.put("counter", counter);
} catch (JSONException | MalformedURLException ex) {
error = ex;
return;
}

RequestBody body = RequestBody.create(requestBody.toString(), Helper.JSON_MEDIA_TYPE);
Request.Builder requestBuilder = new Request.Builder().url(notifyUrl).post(body);
requestBuilder.addHeader("X-Lbry-Auth-Token", authToken);
requestBuilder.addHeader("Tus-Resumable", "1.0.0");
Request request = requestBuilder.build();
OkHttpClient client = new OkHttpClient.Builder()
.writeTimeout(300, TimeUnit.SECONDS)
.readTimeout(300, TimeUnit.SECONDS)
.build();

try {
Response response = client.newCall(request).execute();

try {
Lbry.parseResponse(response);
} catch (LbryResponseException ex) {
if (ex.getMessage() != null &&
ex.getMessage().equalsIgnoreCase("upload is still in process")) {
Thread.sleep(NOTIFY_RETRY_INTERVAL);
makeNotifyRequest(uploadUrl);
return;
}
}

sendStatusRequest(uploadUrl, STATUS_RETRY_COUNT);
} catch (IOException | InterruptedException ex) {
error = ex;
}
}

void sendStatusRequest(String uploadUrl, int retryCount) {
try {
URL statusUrl = new URL(uploadUrl + "/status");
Request.Builder requestBuilder = new Request.Builder().url(statusUrl).get();
requestBuilder.addHeader("Content-Type", "application/json");
requestBuilder.addHeader("X-Lbry-Auth-Token", authToken);
requestBuilder.addHeader("Tus-Resumable", "1.0.0");
Request request = requestBuilder.build();
OkHttpClient client = new OkHttpClient.Builder()
.writeTimeout(300, TimeUnit.SECONDS)
.readTimeout(300, TimeUnit.SECONDS)
.build();

Response response = client.newCall(request).execute();

switch (response.code()) {
case 200:
JSONObject result = (JSONObject) Lbry.parseResponse(response);
if (result.has("outputs")) {
JSONArray outputs = result.getJSONArray("outputs");
for (int i = 0; i < outputs.length(); i++) {
JSONObject output = outputs.getJSONObject(i);
if (output.has("claim_id") && output.has("claim_op")) {
claimResult = Claim.claimFromOutput(output);
break;
}
}
}
break;

case 202:
if (retryCount > 0) {
Thread.sleep(STATUS_RETRY_INTERVAL);
sendStatusRequest(uploadUrl, retryCount - 1);
} else {
error = new CheckStatusException(
"The file is still being processed. Check back later after a few minutes.",
uploadUrl
);
}
break;

case 403:
case 404:
error = new LbryResponseException("The upload does not exist");
break;

case 409:
// Get SDK error from response
try {
Lbry.parseResponse(response);
} catch (LbryResponseException ex) {
if (ex.getMessage() != null) {
error = new LbryResponseException("Failed to process the uploaded file: " + ex.getMessage());
} else {
error = new LbryResponseException("Failed to process the uploaded file");
}
}
break;

default:
error = new LbryResponseException("Unexpected error: " + response.code());
}
} catch (IOException | InterruptedException | LbryResponseException | JSONException ex) {
error = ex;
}
}

@Override
protected void onPostExecute(Void unused) {
Helper.setViewVisibility(progressView, View.GONE);
Expand All @@ -148,4 +248,15 @@ protected void onPostExecute(Void unused) {
}
}
}

// Not actually an exception, just a handy type for returning a value.
// Used when status request returns 202 and it's exceeded retry count.
public static class CheckStatusException extends Exception {
@Getter
private final String uploadUrl;
public CheckStatusException(String message, String uploadUrl) {
super(message);
this.uploadUrl = uploadUrl;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public class PublishFormFragment extends BaseFragment implements
private boolean editMode;
@Getter
private boolean saveInProgress;
private String pendingStatusUploadUrl;
private String currentFilter;
private boolean publishFileChecked;
private boolean fetchingChannels;
Expand Down Expand Up @@ -1268,12 +1269,22 @@ public void onSuccess(Claim claimResult) {

@Override
public void onError(Exception error) {
if (error instanceof TusPublishTask.CheckStatusException) {
pendingStatusUploadUrl = ((TusPublishTask.CheckStatusException) error).getUploadUrl();
postSave();
showMessage(error.getMessage());
Helper.setViewText(buttonPublish, R.string.check_status);
return;
}

showError(error.getMessage());
postSave();
}
};

if (!editMode) {
TusPublishTask task = new TusPublishTask(claim, finalFilePath, progressPublish, Lbryio.AUTH_TOKEN, handler);
TusPublishTask task = new TusPublishTask(claim, finalFilePath,
pendingStatusUploadUrl, progressPublish, Lbryio.AUTH_TOKEN, handler);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
PublishClaimTask task = new PublishClaimTask(claim, progressPublish, Lbryio.AUTH_TOKEN, handler);
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/res/layout/fragment_publish_form.xml
Original file line number Diff line number Diff line change
Expand Up @@ -631,10 +631,13 @@
<ProgressBar
android:id="@+id/publish_form_publishing"
android:layout_centerVertical="true"
android:layout_width="20dp"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_toStartOf="@id/publish_form_publish_button"
android:layout_toEndOf="@id/publish_form_cancel"
style="?android:attr/progressBarStyleHorizontal"
android:visibility="gone" />

<com.google.android.material.button.MaterialButton
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@
</plurals>

<!-- Publish -->
<string name="check_status">Check Status</string>
<string name="no_publishes_created">It looks like you have not published content to LBRY yet.</string>
<string name="record">Record</string>
<string name="take_photo">Take a Photo</string>
Expand Down