Skip to content

Commit

Permalink
feat: add cancelUpload and AbortController for backgroundUpload (#238)
Browse files Browse the repository at this point in the history
1. cancelUpload
2. cancelUpload all
3. AbortController
  • Loading branch information
numandev1 authored Nov 15, 2023
1 parent 7696baf commit 364b84b
Show file tree
Hide file tree
Showing 21 changed files with 353 additions and 56 deletions.
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,53 @@ const uploadResult = await backgroundUpload(
);
```

### Cancel Background Upload
for cancellation Upload, there is two ways
1. by calling, cancelUpload function
2. by calling abort function

##### cancelUpload (support single and all)
```js
import { cancelUpload, backgroundUpload } from 'react-native-compressor';

// if we will call without passing any param then it will remove last pushed uploading
cancelUpload()

// if you pass true as second param then it will cancel all the uploading
cancelUpload("",true)

// if there is multiple files are uploading, and you wanna cancel specific uploading then you pass specific video id like this
let videoId=''
const uploadResult = await backgroundUpload(
url,
fileUrl,
{ httpMethod: 'PUT', getCancellationId: (cancellationId) =>(videoId = cancellationId), },
(written, total) => {
console.log(written, total);
}
);
cancelUpload(videoId)
```

##### cancel by calling abort
```js
import { backgroundUpload } from 'react-native-compressor';

const abortSignalRef = useRef(new AbortController());

const uploadResult = await backgroundUpload(
url,
fileUrl,
{ httpMethod: 'PUT' },
(written, total) => {
console.log(written, total);
},
abortSignalRef.current.signal
);

abortSignalRef.current?.abort(); // this will cancel uploading
```
### Download File
```js
Expand Down Expand Up @@ -493,12 +540,25 @@ export declare type UploaderOptions = (
) & {
headers?: Record<string, string>;
httpMethod?: UploaderHttpMethod;
getCancellationId?: (cancellationId: string) => void;
};
```

**Note:** some of the uploader code is borrowed from [Expo](https://github.com/expo/expo)
I tested file uploader on this backend [Nodejs-File-Uploader](https://github.com/numandev1/nodejs-file-uploader)

### Cancel Background Upload
for cancellation Upload, there is two ways, you can use one of it
- ##### cancelUpload: ( uuid?: string, shouldCancelAll?: boolean) => void
1. If we call without passing any param then it will remove the last pushed uploading
2. If you pass true as the second param then it will cancel all the uploading
3. if there is multiple files are uploading, and you wanna cancel specific uploading then you pass a specific video ID like this

- ##### we can use [AbortController](https://github.com/facebook/react-native/blob/255fef5263afdf9933ba2f8a3dbcbca39ea9928a/packages/react-native/types/modules/globals.d.ts#L531) in backgroundUpload [Usage](#cancel-background-upload)
`const abortSignalRef = useRef(new AbortController());`

`abortSignalRef.current?.abort();`

### Download

- ##### download: ( fileUrl: string, downloadProgress?: (progress: number) => void, progressDivider?: number ) => Promise< string >
Expand Down
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ android {
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

sourceSets {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ class CompressorModule(private val reactContext: ReactApplicationContext) : Comp
uploader.upload(fileUrl, options, reactContext, promise)
}

@ReactMethod
override fun cancelUpload(uuid: String,shouldCancelAll:Boolean) {
uploader.cancelUpload(uuid,shouldCancelAll)
}

@ReactMethod
override fun download(
fileUrl: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.reactnativecompressor.Utils

import okhttp3.Call

class HttpCallManager {
private var resumableCalls: MutableMap<String, Call?> = HashMap()

fun registerTask(call: Call, uuid: String) {
resumableCalls[uuid] = call
}

fun taskForId(uuid: String): Call? {
return resumableCalls[uuid]
}

// will use in future
fun downloadTaskForId(uuid: String): Call? {
return taskForId(uuid)
}

fun uploadTaskForId(uuid: String): Call? {
return taskForId(uuid)
}

fun taskPop(): Call? {
val lastUuid = resumableCalls.keys.lastOrNull()
val lastCall = resumableCalls.remove(lastUuid)
return lastCall
}

fun unregisterTask(uuid: String) {
resumableCalls.remove(uuid)
}

fun cancelAllTasks() {
for ((_, call) in resumableCalls) {
call?.cancel()
}
resumableCalls.clear()
}
}
103 changes: 70 additions & 33 deletions android/src/main/java/com/reactnativecompressor/Utils/Uploader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,36 +26,45 @@ import java.net.URLConnection
import java.util.concurrent.TimeUnit

class Uploader(private val reactContext: ReactApplicationContext) {
val TAG = "asyncTaskUploader"
var client: OkHttpClient? = null
val MIN_EVENT_DT_MS: Long = 100

fun upload(fileUriString: String, _options: ReadableMap, reactContext: ReactApplicationContext, promise: Promise) {
val options:UploaderOptions=convertReadableMapToUploaderOptions(_options)
val url = options.url
val uuid = options.uuid
val progressListener: CountingRequestListener = object : CountingRequestListener {
private var mLastUpdate: Long = -1
override fun onProgress(bytesWritten: Long, contentLength: Long) {
val currentTime = System.currentTimeMillis()

// Throttle events. Sending too many events will block the JS event loop.
// Make sure to send the last event when we're at 100%.
if (currentTime > mLastUpdate + MIN_EVENT_DT_MS || bytesWritten == contentLength) {
mLastUpdate = currentTime
EventEmitterHandler.sendUploadProgressEvent(bytesWritten,contentLength,uuid)
}
val TAG = "asyncTaskUploader"
var client: OkHttpClient? = null
val MIN_EVENT_DT_MS: Long = 100
val httpCallManager = HttpCallManager()

fun upload(
fileUriString: String,
_options: ReadableMap,
reactContext: ReactApplicationContext,
promise: Promise
) {
val options: UploaderOptions = convertReadableMapToUploaderOptions(_options)
val url = options.url
val uuid = options.uuid
val progressListener: CountingRequestListener = object : CountingRequestListener {
private var mLastUpdate: Long = -1
override fun onProgress(bytesWritten: Long, contentLength: Long) {
val currentTime = System.currentTimeMillis()

// Throttle events. Sending too many events will block the JS event loop.
// Make sure to send the last event when we're at 100%.
if (currentTime > mLastUpdate + MIN_EVENT_DT_MS || bytesWritten == contentLength) {
mLastUpdate = currentTime
EventEmitterHandler.sendUploadProgressEvent(bytesWritten, contentLength, uuid)
}
}
val request = createUploadRequest(
url, fileUriString, options
) { requestBody -> CountingRequestBody(requestBody, progressListener) }

okHttpClient?.let {
it.newCall(request).enqueue(object : Callback {
}
val request = createUploadRequest(
url, fileUriString, options
) { requestBody -> CountingRequestBody(requestBody, progressListener) }

okHttpClient?.let {
val call = it.newCall(request)
httpCallManager.registerTask(call,uuid)
call.enqueue(
object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.e(TAG, e.message.toString())
promise.reject(TAG, e.message, e)
promise.reject(TAG, e.message)
}

override fun onResponse(call: Call, response: Response) {
Expand All @@ -67,12 +76,12 @@ class Uploader(private val reactContext: ReactApplicationContext) {
promise.resolve(param)
}
})
} ?: run {
promise.reject(UploaderOkHttpNullException())
}

} ?: run {
promise.reject(UploaderOkHttpNullException())
}

}

@get:Synchronized
private val okHttpClient: OkHttpClient?
get() {
Expand All @@ -87,7 +96,12 @@ class Uploader(private val reactContext: ReactApplicationContext) {
}

@Throws(IOException::class)
private fun createUploadRequest(url: String, fileUriString: String, options: UploaderOptions, decorator: RequestBodyDecorator): Request {
private fun createUploadRequest(
url: String,
fileUriString: String,
options: UploaderOptions,
decorator: RequestBodyDecorator
): Request {
val fileUri = Uri.parse(slashifyFilePath(fileUriString))
fileUri.checkIfFileExists()

Expand All @@ -101,7 +115,11 @@ class Uploader(private val reactContext: ReactApplicationContext) {
}

@SuppressLint("NewApi")
private fun createRequestBody(options: UploaderOptions, decorator: RequestBodyDecorator, file: File): RequestBody {
private fun createRequestBody(
options: UploaderOptions,
decorator: RequestBodyDecorator,
file: File
): RequestBody {
return when (options.uploadType) {
UploadType.BINARY_CONTENT -> {
val mimeType: String? = if (options.mimeType?.isNotEmpty() == true) {
Expand All @@ -122,7 +140,11 @@ class Uploader(private val reactContext: ReactApplicationContext) {
val mimeType: String = options.mimeType ?: URLConnection.guessContentTypeFromName(file.name)

val fieldName = options.fieldName ?: file.name
bodyBuilder.addFormDataPart(fieldName, file.name, decorator.decorate(file.asRequestBody(mimeType.toMediaTypeOrNull())))
bodyBuilder.addFormDataPart(
fieldName,
file.name,
decorator.decorate(file.asRequestBody(mimeType.toMediaTypeOrNull()))
)
bodyBuilder.build()
}
}
Expand Down Expand Up @@ -175,4 +197,19 @@ class Uploader(private val reactContext: ReactApplicationContext) {
}
return responseHeaders
}

fun cancelUpload(uuid:String,shouldCancelAll:Boolean) {
if(shouldCancelAll)
{
httpCallManager.cancelAllTasks()
}
else if(uuid=="")
{
httpCallManager.taskPop()?.cancel()
}
else
{
httpCallManager.uploadTaskForId(uuid)?.cancel()
}
}
}
1 change: 1 addition & 0 deletions android/src/oldarch/CompressorSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ abstract class CompressorSpec(context: ReactApplicationContext?) : ReactContextB
abstract fun compress(fileUrl: String, optionMap: ReadableMap, promise: Promise)
abstract fun cancelCompression(uuid: String)
abstract fun upload(fileUrl: String, options: ReadableMap, promise: Promise)
abstract fun cancelUpload(uuid: String, shouldCancelAll:Boolean)

abstract fun download(fileUrl: String, options: ReadableMap, promise: Promise)
abstract fun activateBackgroundTask(options: ReadableMap, promise: Promise)
Expand Down
8 changes: 8 additions & 0 deletions example/ios/CompressorExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,10 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = CompressorExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -500,6 +503,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = CompressorExample;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
Expand All @@ -512,7 +516,10 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = CompressorExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand All @@ -526,6 +533,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = CompressorExample;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
Expand Down
1 change: 1 addition & 0 deletions example/ios/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ GEM
rexml (~> 3.2.4)

PLATFORMS
arm64-darwin-22
x86_64-darwin-19
x86_64-darwin-20
x86_64-darwin-22
Expand Down
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,7 @@ PODS:
- React-Codegen
- React-RCTFabric
- ReactCommon/turbomodule/core
- react-native-compressor (1.8.16):
- react-native-compressor (1.8.17):
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- RCTRequired
Expand Down Expand Up @@ -1377,7 +1377,7 @@ SPEC CHECKSUMS:
React-jsinspector: 194e32c6aab382d88713ad3dd0025c5f5c4ee072
React-logger: cebf22b6cf43434e471dc561e5911b40ac01d289
react-native-cameraroll: 5d9523136a929b58f092fd7f0a9a13367a4b46e3
react-native-compressor: 3ad769f5bac56d7337df076a1c62e74292f7136d
react-native-compressor: 8b6e302c4531f93aeaabd9658495bf9855adf3bd
react-native-document-picker: c9ac93d7b511413f4a0ed61c92ff6c7b1bcf4f94
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
react-native-image-picker: 9b4b1d0096500050cbdabf8f4fd00b771065d983
Expand Down
6 changes: 4 additions & 2 deletions example/src/Screens/Main/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StackNavigationProp } from '@react-navigation/stack';
import { type StackNavigationProp } from '@react-navigation/stack';
import React from 'react';
import {
FlatList,
Expand All @@ -7,9 +7,11 @@ import {
Pressable,
ScrollView,
View,
type StyleProp,
type ViewStyle,
} from 'react-native';

import { SCREENS, Screens } from '..';
import { SCREENS, type Screens } from '..';

type RootStackParams = { Home: undefined } & { [key: string]: undefined };
type MainScreenProps = {
Expand Down
Loading

0 comments on commit 364b84b

Please sign in to comment.