Skip to content
Open
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
2 changes: 1 addition & 1 deletion RNTusClient.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ Pod::Spec.new do |s|
s.ios.deployment_target = '8.0'

s.dependency 'React-Core'
s.dependency 'TUSKit'
s.dependency 'TUSKit', '1.4.2'
end
3 changes: 1 addition & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ repositories {
}

dependencies {
compile 'com.facebook.react:react-native:+'
compile group: 'org.jetbrains', name: 'annotations', version: '13.0'
implementation 'com.facebook.react:react-native:+'
implementation 'io.tus.java.client:tus-java-client:0.4.2'
implementation 'io.tus.android.client:tus-android-client:0.1.9'
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package com.vinzscam.rntusclient;

import android.content.SharedPreferences;
import android.net.Uri;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
Expand All @@ -12,13 +13,16 @@
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import java.lang.Exception;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
Expand All @@ -28,10 +32,10 @@
import java.util.concurrent.atomic.AtomicBoolean;

import io.tus.android.client.TusPreferencesURLStore;
import io.tus.android.client.TusAndroidUpload;
import io.tus.java.client.ProtocolException;
import io.tus.java.client.TusClient;
import io.tus.java.client.TusExecutor;
import io.tus.java.client.TusUpload;
import io.tus.java.client.TusUploader;

public class RNTusClientModule extends ReactContextBaseJavaModule {
Expand Down Expand Up @@ -63,31 +67,28 @@ public void createUpload(String fileUrl, ReadableMap options, Callback callback)
Map<String, Object> rawMetadata = options.getMap("metadata").toHashMap();

Map<String, String> metadata = new HashMap<>();
for(String key: rawMetadata.keySet()) {
for (String key : rawMetadata.keySet()) {
metadata.put(key, String.valueOf(rawMetadata.get(key)));
}
Map<String, String> headers = new HashMap<>();
for(String key: rawHeaders.keySet()) {
for (String key : rawHeaders.keySet()) {
headers.put(key, String.valueOf(rawHeaders.get(key)));
}

File file = new File(fileUrl);
TusUpload upload = null;
try {
upload = new TusUpload(file);
String uploadId = UUID.randomUUID().toString();
TusRunnable executor = new TusRunnable(fileUrl, uploadId, endpoint, metadata, headers);
this.executorsMap.put(uploadId, executor);
callback.invoke(uploadId);
} catch(FileNotFoundException | MalformedURLException e) {
callback.invoke((Object)null, e.getMessage());
} catch (Exception e) {
callback.invoke((Object) null, e.getMessage());
}
}

@ReactMethod
public void resume(String uploadId, Callback callback) {
TusRunnable executor = this.executorsMap.get(uploadId);
if(executor != null) {
if (executor != null) {
pool.submit(executor);
callback.invoke(true);
} else {
Expand All @@ -99,39 +100,38 @@ public void resume(String uploadId, Callback callback) {
public void abort(String uploadId, Callback callback) {
try {
TusRunnable executor = this.executorsMap.get(uploadId);
if(executor != null) {
if (executor != null) {
executor.finish();
}
callback.invoke((Object)null);
} catch(IOException | ProtocolException e) {
callback.invoke((Object) null);
} catch (Exception e) {
callback.invoke(e);
}
}

class TusRunnable extends TusExecutor implements Runnable {
private TusUpload upload;
private TusAndroidUpload upload;
private TusUploader uploader;
private String uploadId;
private TusClient client;
private boolean shouldFinish;
private boolean isRunning;

public TusRunnable(String fileUrl,
String uploadId,
String endpoint,
Map<String, String> metadata,
Map<String, String>headers
) throws FileNotFoundException, MalformedURLException {
public TusRunnable(String fileUrl, String uploadId, String endpoint, Map<String, String> metadata,
Map<String, String> headers) throws FileNotFoundException, MalformedURLException {
this.uploadId = uploadId;

client = new TusClient();
client.setUploadCreationURL(new URL(endpoint));

SharedPreferences pref = getReactApplicationContext().getSharedPreferences("tus", 0);
SharedPreferences pref = reactContext.getSharedPreferences("tus", 0);

client.enableResuming(new TusPreferencesURLStore(pref));
client.setHeaders(headers);
File file = new File(fileUrl);
upload = new TusUpload((file));

upload = new TusAndroidUpload(Uri.parse(fileUrl), reactContext);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the second parameter supposed to be of type "Activity" as per the following documentation https://tus.github.io/tus-android-client/javadoc/io/tus/android/client/TusAndroidUpload.html ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! I tried with getCurrentActivity() too, but for some reason it was not working properly for me (Xiaomi Mi 9). Then I tried with reactContext instead and it worked great.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result is currently in production btw:
https://play.google.com/store/apps/details?id=io.starchive.mobile

upload.setMetadata(metadata);

shouldFinish = false;
isRunning = false;
}
Expand All @@ -141,26 +141,38 @@ protected void makeAttempt() throws ProtocolException, IOException {
uploader.setChunkSize(1024);
uploader.setRequestPayloadSize(10 * 1024 * 1024);

do {
long totalBytes = upload.getSize();
long bytesUploaded = uploader.getOffset();
WritableMap params = Arguments.createMap();
params.putString("uploadId", uploadId);
params.putDouble("bytesWritten", bytesUploaded);
params.putDouble("bytesTotal", totalBytes);
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(ON_PROGRESS, params);
}while(uploader.uploadChunk() > -1 && !shouldFinish);
Timer progressTicker = new Timer();

progressTicker.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
sendProgressEvent(upload.getSize(), uploader.getOffset());
}
}, 0, 500);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should make the period's value customizable from javascript!


do {} while (uploader.uploadChunk() > -1 && !shouldFinish);

sendProgressEvent(upload.getSize(), upload.getSize());
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

swapping the two methods could avoid some unnecessary progress events' calls:

progressTicker.cancel();
sendProgressEvent(upload.getSize(), upload.getSize());


progressTicker.cancel();
uploader.finish();
}

private void sendProgressEvent(long bytesTotal, long bytesUploaded) {
WritableMap params = Arguments.createMap();

params.putString("uploadId", uploadId);
params.putDouble("bytesWritten", bytesUploaded);
params.putDouble("bytesTotal", bytesTotal);

reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(ON_PROGRESS, params);
}

public void finish() throws ProtocolException, IOException {
if(isRunning) {
if (isRunning) {
shouldFinish = true;
}
else {
if(uploader!= null){
} else {
if (uploader != null) {
uploader.finish();
}
}
Expand All @@ -176,16 +188,14 @@ public void run() {
WritableMap params = Arguments.createMap();
params.putString("uploadId", uploadId);
params.putString("uploadUrl", uploadUrl);
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(ON_SUCCESS, params);
} catch (ProtocolException | IOException e) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(ON_SUCCESS, params);
} catch (Exception e) {
WritableMap params = Arguments.createMap();
params.putString("uploadId", uploadId);
params.putString("error", e.toString());
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(ON_ERROR, params);
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(ON_ERROR, params);
}
isRunning = false;
}
}
}
}