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
249 changes: 128 additions & 121 deletions README.md

Large diffs are not rendered by default.

32 changes: 22 additions & 10 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ buildscript {
}

ext {
ext.kotlinVersion = safeExtGet('kotlinVersion', '1.6.0')
buildToolsVersion = '29.0.2'
compileSdkVersion = 29
agpVersion = '7.4.0'
ext.kotlinVersion = safeExtGet('kotlinVersion', '1.9.22')
buildToolsVersion = '34.0.0'
compileSdkVersion = 34
targetSdkVersion = 29
minSdkVersion = 18
}
Expand All @@ -17,17 +18,17 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath "com.android.tools.build:gradle:$agpVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

def DEFAULT_COMPILE_SDK_VERSION = 28
def DEFAULT_BUILD_TOOLS_VERSION = "28.0.3"
def DEFAULT_TARGET_SDK_VERSION = 28
def DEFAULT_COMPILE_SDK_VERSION = 31
def DEFAULT_BUILD_TOOLS_VERSION = "30.0.2"
def DEFAULT_TARGET_SDK_VERSION = 29

def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
Expand Down Expand Up @@ -59,15 +60,26 @@ def _ext = ext

def _kotlinVersion = _ext.has('detoxKotlinVersion') ? _ext.detoxKotlinVersion : '1.3.10'
def _kotlinStdlib = _ext.has('detoxKotlinStdlib') ? _ext.detoxKotlinStdlib : 'kotlin-stdlib-jdk8'
def _workVersion = "2.9.0"
def _uploadServiceVersion = "4.9.2"

dependencies {
implementation "androidx.core:core-ktx:1.0.1"

implementation "androidx.work:work-runtime:$_workVersion"
implementation "androidx.work:work-runtime-ktx:$_workVersion"

//noinspection GradleDynamicVersion
implementation "androidx.core:core-ktx:1.12.0"

implementation 'com.facebook.react:react-native:+'

implementation "org.jetbrains.kotlin:$_kotlinStdlib:$_kotlinVersion"

implementation 'net.gotev:uploadservice-okhttp:4.9.2'
//noinspection GradleDependency
implementation "net.gotev:uploadservice:$_uploadServiceVersion"
//noinspection GradleDependency
implementation "net.gotev:uploadservice-okhttp:$_uploadServiceVersion"

implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.google.code.gson:gson:2.10.1'
}
2 changes: 1 addition & 1 deletion android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.vydia.RNUploader">
package="com.sitemate.uploader">
</manifest>
63 changes: 63 additions & 0 deletions android/src/main/java/com/sitemate/extensions/ContextExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.sitemate.extensions

import android.content.Context
import com.sitemate.work.TaskCompletionNotifier
import com.sitemate.work.UploadManager
import net.gotev.uploadservice.UploadTask
import net.gotev.uploadservice.data.UploadNotificationConfig
import net.gotev.uploadservice.data.UploadTaskParameters
import net.gotev.uploadservice.logger.UploadServiceLogger
import net.gotev.uploadservice.observer.task.BroadcastEmitter

data class UploadTaskCreationParameters(
val params: UploadTaskParameters,
val notificationConfig: UploadNotificationConfig
)

/**
* Creates a new task instance based on the requested task class in the intent.
* @param creationParameters task creation params
* @return task instance or null if the task class is not supported or invalid
*/
@Suppress("UNCHECKED_CAST")
fun Context.getUploadTask(
creationParameters: UploadTaskCreationParameters,
notificationId: Int
): UploadTask? {
return try {
val observers = arrayOf(
BroadcastEmitter(this),
TaskCompletionNotifier()
)

val taskClass = Class.forName(creationParameters.params.taskClass) as Class<out UploadTask>
val uploadTask = taskClass.newInstance().apply {
init(
context = this@getUploadTask,
taskParams = creationParameters.params,
notificationConfig = creationParameters.notificationConfig,
notificationId = notificationId,
taskObservers = observers
)
}

UploadServiceLogger.debug(
component = UploadManager.TAG,
uploadId = creationParameters.params.id,
message = {
"Successfully created new task with class: ${taskClass.name}"
}
)
uploadTask
} catch (exc: Throwable) {
UploadServiceLogger.error(
component = UploadManager.TAG,
uploadId = "",
exception = exc,
message = {
"Error while instantiating new task"
}
)
null
}
}
57 changes: 57 additions & 0 deletions android/src/main/java/com/sitemate/extensions/UploadExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.sitemate.extensions

import androidx.work.Constraints
import androidx.work.Data
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import com.google.gson.Gson
import net.gotev.uploadservice.data.UploadFile
import net.gotev.uploadservice.data.UploadNotificationConfig
import net.gotev.uploadservice.data.UploadTaskParameters
import net.gotev.uploadservice.extensions.setOrRemove
import net.gotev.uploadservice.persistence.PersistableData

private const val PROPERTY_PARAM_NAME = "multipartParamName"
private const val PROPERTY_REMOTE_FILE_NAME = "multipartRemoteFileName"
private const val PROPERTY_CONTENT_TYPE = "multipartContentType"
const val PARAM_KEY_TASK_PARAMS = "PARAM_KEY_TASK_PARAMS"
const val PARAM_KEY_NOTIF_CONFIG = "PARAM_KEY_NOTIF_CONFIG"

internal var UploadFile.parameterName: String?
get() = properties[PROPERTY_PARAM_NAME]
set(value) {
properties.setOrRemove(PROPERTY_PARAM_NAME, value)
}

internal var UploadFile.remoteFileName: String?
get() = properties[PROPERTY_REMOTE_FILE_NAME]
set(value) {
properties.setOrRemove(PROPERTY_REMOTE_FILE_NAME, value)
}

internal var UploadFile.contentType: String?
get() = properties[PROPERTY_CONTENT_TYPE]
set(value) {
properties.setOrRemove(PROPERTY_CONTENT_TYPE, value)
}

internal fun UploadTaskParameters.toJson(): String = toPersistableData().toJson()

internal fun String.toUploadTaskParameters(): UploadTaskParameters = UploadTaskParameters.createFromPersistableData(PersistableData.fromJson(this))

internal fun UploadNotificationConfig.toJson(): String = Gson().toJson(this)

internal fun String.toUploadNotificationConfig() = Gson().fromJson(this, UploadNotificationConfig::class.java)

internal fun OneTimeWorkRequest.Builder.setData(uploadTaskParameters: UploadTaskParameters, notificationConfig: UploadNotificationConfig) {
val data = Data.Builder()
data.putString(PARAM_KEY_TASK_PARAMS, uploadTaskParameters.toJson())
data.putString(PARAM_KEY_NOTIF_CONFIG, notificationConfig.toJson())
setInputData(data.build())
}

internal fun OneTimeWorkRequest.Builder.shouldLimitNetwork(limit: Boolean) {
val network = if (limit) NetworkType.UNMETERED else NetworkType.CONNECTED
val constraints = Constraints.Builder().setRequiredNetworkType(network).build()
setConstraints(constraints)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.vydia.RNUploader
package com.sitemate.uploader

import android.content.Context
import android.util.Log
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.sitemate.uploader

import android.content.Context
import net.gotev.uploadservice.UploadTask
import net.gotev.uploadservice.data.UploadFile
import net.gotev.uploadservice.protocols.binary.BinaryUploadRequest
import net.gotev.uploadservice.protocols.binary.BinaryUploadTask
import java.io.FileNotFoundException
import java.io.IOException

class ModifiedBinaryUploadRequest(context: Context, serverUrl: String, limitNetwork: Boolean) :
ModifiedHttpUploadRequest<ModifiedBinaryUploadRequest>(context, serverUrl, limitNetwork) {

override val taskClass: Class<out UploadTask>
get() = BinaryUploadTask::class.java

/**
* Sets the file used as raw body of the upload request.
*
* @param path path to the file that you want to upload
* @throws FileNotFoundException if the file to upload does not exist
* @return [BinaryUploadRequest]
*/
@Throws(IOException::class)
fun setFileToUpload(path: String): ModifiedBinaryUploadRequest {
files.clear()
files.add(UploadFile(path))
return this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.sitemate.uploader

import android.content.Context
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.sitemate.extensions.setData
import com.sitemate.extensions.shouldLimitNetwork
import com.sitemate.work.UploadManager
import com.sitemate.work.UploadWorker
import net.gotev.uploadservice.HttpUploadRequest
import net.gotev.uploadservice.data.UploadTaskParameters
import java.util.*

abstract class ModifiedHttpUploadRequest<B : HttpUploadRequest<B>>(context: Context, serverUrl: String, private val limitNetwork: Boolean = false) :
HttpUploadRequest<B>(context, serverUrl) {

private var started: Boolean = false
private var uploadId = UUID.randomUUID().toString()
private val uploadTaskParameters: UploadTaskParameters
get() = UploadTaskParameters(
taskClass = taskClass.name,
id = uploadId,
serverUrl = serverUrl,
maxRetries = maxRetries,
autoDeleteSuccessfullyUploadedFiles = autoDeleteSuccessfullyUploadedFiles,
files = files,
additionalParameters = getAdditionalParameters()
)

override fun startUpload(): String {
require(files.isNotEmpty()) { "Set the file to be used in the request body first!" }
check(!started) {
"You have already called startUpload() on this Upload request instance once and you " +
"cannot call it multiple times. Check your code."
}

check(!UploadManager.taskList.contains(uploadTaskParameters.id)) {
"You have tried to perform startUpload() using the same uploadID of an " +
"already running task. You're trying to use the same ID for multiple uploads."
}

started = true
val workManager: WorkManager = WorkManager.getInstance(context)
val uploadRequest = OneTimeWorkRequest.Builder(UploadWorker::class.java)
uploadRequest.shouldLimitNetwork(limitNetwork)
uploadRequest.addTag("${UploadWorker::class.java.simpleName}-$uploadId")
uploadRequest.setData(uploadTaskParameters, notificationConfig(context, uploadId))
workManager.enqueue(uploadRequest.build())

return uploadTaskParameters.id;
}

fun setCustomUploadID(uploadID: String) {
this.uploadId = uploadID
setUploadID(uploadID)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.sitemate.uploader

import android.content.Context
import com.sitemate.extensions.contentType
import com.sitemate.extensions.parameterName
import com.sitemate.extensions.remoteFileName
import net.gotev.uploadservice.UploadTask
import net.gotev.uploadservice.data.UploadFile
import net.gotev.uploadservice.protocols.multipart.MultipartUploadTask
import java.io.FileNotFoundException

class ModifiedMultipartUploadRequest(context: Context, serverUrl: String, limitNetwork: Boolean) :
ModifiedHttpUploadRequest<ModifiedMultipartUploadRequest>(context, serverUrl, limitNetwork) {

override val taskClass: Class<out UploadTask>
get() = MultipartUploadTask::class.java

/**
* Adds a file to this upload request.
*
* @param filePath path to the file that you want to upload
* @param parameterName Name of the form parameter that will contain file's data
* @param fileName File name seen by the server side script. If null, the original file name
* will be used
* @param contentType Content type of the file. If null or empty, the mime type will be
* automatically detected. If fore some reasons autodetection fails,
* `application/octet-stream` will be used by default
* @return [ModifiedMultipartUploadRequest]
*/
@Throws(FileNotFoundException::class)
@JvmOverloads
fun addFileToUpload(
filePath: String,
parameterName: String,
fileName: String? = null,
contentType: String? = null
): ModifiedMultipartUploadRequest {
require(filePath.isNotBlank() && parameterName.isNotBlank()) {
"Please specify valid filePath and parameterName. They cannot be blank."
}

files.add(UploadFile(filePath).apply {
this.parameterName = parameterName

this.contentType = if (contentType.isNullOrBlank()) {
handler.contentType(context)
} else {
contentType
}

remoteFileName = if (fileName.isNullOrBlank()) {
handler.name(context)
} else {
fileName
}
})

return this
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.vydia.RNUploader
package com.sitemate.uploader

import android.app.Application
import android.app.NotificationChannel
Expand All @@ -7,6 +7,10 @@ import android.content.Context
import android.os.Build
import android.util.Log
import android.webkit.MimeTypeMap
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.sitemate.work.UploadManager
import com.sitemate.work.UploadWorker
import com.facebook.react.BuildConfig
import com.facebook.react.bridge.*
import net.gotev.uploadservice.UploadService
Expand Down Expand Up @@ -37,7 +41,7 @@ class UploaderModule(val reactContext: ReactApplicationContext) : ReactContextBa
Returns an object such as: {extension: "mp4", size: "3804316", exists: true, mimeType: "video/mp4", name: "20161116_074726.mp4"}
*/
@ReactMethod
fun getFileInfo(path: String, promise: Promise) {
fun getFileInfo(path: String?, promise: Promise) {
try {
val params = Arguments.createMap()
val fileInfo = File(path)
Expand All @@ -49,7 +53,7 @@ class UploaderModule(val reactContext: ReactApplicationContext) : ReactContextBa
params.putString("size", fileInfo.length().toString()) //use string form of long because there is no putLong and converting to int results in a max size of 17.2 gb, which could happen. Javascript will need to convert it to a number
val extension = MimeTypeMap.getFileExtensionFromUrl(path)
params.putString("extension", extension)
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.lowercase())
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase())
params.putString("mimeType", mimeType)
}
promise.resolve(params)
Expand All @@ -64,9 +68,9 @@ class UploaderModule(val reactContext: ReactApplicationContext) : ReactContextBa
var followRedirects = true
var followSslRedirects = true
var retryOnConnectionFailure = true
var connectTimeout = 15
var writeTimeout = 30
var readTimeout = 30
var connectTimeout = 45
var writeTimeout = 90
var readTimeout = 90
//TODO: make 'cache' customizable
if (options.hasKey("followRedirects")) {
if (options.getType("followRedirects") != ReadableType.Boolean) {
Expand Down
Loading