Skip to content

Commit b872080

Browse files
authored
Merge pull request #412 from appwrite/feat-resumable-upload-android
Feat resumable upload android
2 parents 5e16084 + fdf33fc commit b872080

File tree

2 files changed

+52
-34
lines changed

2 files changed

+52
-34
lines changed

templates/android/library/src/main/java/io/appwrite/Client.kt.twig

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import okhttp3.RequestBody.Companion.toRequestBody
2323
import java.io.BufferedInputStream
2424
import java.io.BufferedReader
2525
import java.io.File
26+
import java.io.RandomAccessFile
2627
import java.io.IOException
2728
import java.net.CookieManager
2829
import java.net.CookiePolicy
@@ -225,7 +226,7 @@ class Client @JvmOverloads constructor(
225226
headers: Map<String, String> = mapOf(),
226227
params: Map<String, Any?> = mapOf(),
227228
responseType: Class<T>,
228-
convert: ((Map<String, Any,>) -> T)? = null
229+
converter: ((Map<String, Any,>) -> T)? = null
229230
): T {
230231
val filteredParams = params.filterValues { it != null }
231232

@@ -261,7 +262,7 @@ class Client @JvmOverloads constructor(
261262
.get()
262263
.build()
263264

264-
return awaitResponse(request, responseType, convert)
265+
return awaitResponse(request, responseType, converter)
265266
}
266267

267268
val body = if (MultipartBody.FORM.toString() == headers["content-type"]) {
@@ -298,7 +299,7 @@ class Client @JvmOverloads constructor(
298299
.method(method, body)
299300
.build()
300301

301-
return awaitResponse(request, responseType, convert)
302+
return awaitResponse(request, responseType, converter)
302303
}
303304

304305
/**
@@ -316,8 +317,9 @@ class Client @JvmOverloads constructor(
316317
headers: MutableMap<String, String>,
317318
params: MutableMap<String, Any?>,
318319
responseType: Class<T>,
319-
convert: ((Map<String, Any,>) -> T),
320+
converter: ((Map<String, Any,>) -> T),
320321
paramName: String,
322+
idParamName: String? = null,
321323
onProgress: ((UploadProgress) -> Unit)? = null,
322324
): T {
323325
val file = params[paramName] as File
@@ -330,74 +332,84 @@ class Client @JvmOverloads constructor(
330332
file.asRequestBody()
331333
)
332334
return call(
333-
"POST",
335+
method = "POST",
334336
path,
335337
headers,
336338
params,
337339
responseType,
338-
convert
340+
converter
339341
)
340342
}
341343

342-
val input = file.inputStream().buffered()
344+
val input = RandomAccessFile(file, "r")
343345
val buffer = ByteArray(CHUNK_SIZE)
344346
var offset = 0L
345347
var result: Map<*, *>? = null
346348

347-
generateSequence {
348-
val readBytes = input.read(buffer)
349-
if (readBytes >= 0) {
350-
buffer.copyOf(readBytes)
351-
} else {
352-
input.close()
353-
null
354-
}
355-
}.forEach {
349+
if (idParamName?.isNotEmpty() == true && params[idParamName] != "unique()") {
350+
// Make a request to check if a file already exists
351+
val current = call(
352+
method = "GET",
353+
path = "$path/${params[idParamName]}",
354+
headers = headers,
355+
params = emptyMap(),
356+
responseType = Map::class.java,
357+
)
358+
val chunksUploaded = current["chunksUploaded"] as Long
359+
offset = (chunksUploaded * CHUNK_SIZE).coerceAtMost(size)
360+
}
361+
362+
while (offset < size) {
363+
input.seek(offset)
364+
input.read(buffer)
365+
356366
params[paramName] = MultipartBody.Part.createFormData(
357367
paramName,
358368
file.name,
359-
it.toRequestBody()
369+
buffer.toRequestBody()
360370
)
361371

362372
headers["Content-Range"] =
363373
"bytes $offset-${((offset + CHUNK_SIZE) - 1).coerceAtMost(size)}/$size"
364374

365375
result = call(
366-
"POST",
376+
method = "POST",
367377
path,
368378
headers,
369379
params,
370-
Map::class.java
380+
responseType = Map::class.java
371381
)
372382

373383
offset += CHUNK_SIZE
374-
headers["x-{{ spec.title | caseLower }}-id"] = result!!["\$id"].toString()
375-
onProgress?.invoke(UploadProgress(
376-
id = result!!["\$id"].toString(),
377-
progress = offset.coerceAtMost(size).toDouble()/size * 100,
378-
sizeUploaded = offset.coerceAtMost(size),
379-
chunksTotal = result!!["chunksTotal"].toString().toInt(),
380-
chunksUploaded = result!!["chunksUploaded"].toString().toInt(),
381-
))
384+
headers["x-appwrite-id"] = result!!["\$id"].toString()
385+
onProgress?.invoke(
386+
UploadProgress(
387+
id = result!!["\$id"].toString(),
388+
progress = offset.coerceAtMost(size).toDouble() / size * 100,
389+
sizeUploaded = offset.coerceAtMost(size),
390+
chunksTotal = result!!["chunksTotal"].toString().toInt(),
391+
chunksUploaded = result!!["chunksUploaded"].toString().toInt(),
392+
)
393+
)
382394
}
383395

384-
return convert(result as Map<String, Any>)
396+
return converter(result as Map<String, Any>)
385397
}
386398

387399
/**
388400
* Await Response
389401
*
390402
* @param request
391403
* @param responseType
392-
* @param convert
404+
* @param converter
393405
*
394406
* @return [T]
395407
*/
396408
@Throws({{ spec.title | caseUcfirst }}Exception::class)
397409
private suspend fun <T> awaitResponse(
398410
request: Request,
399411
responseType: Class<T>,
400-
convert: ((Map<String, Any,>) -> T)? = null
412+
converter: ((Map<String, Any,>) -> T)? = null
401413
) = suspendCancellableCoroutine<T> {
402414
http.newCall(request).enqueue(object : Callback {
403415
override fun onFailure(call: Call, e: IOException) {
@@ -463,7 +475,7 @@ class Client @JvmOverloads constructor(
463475
object : TypeToken<Map<String, Any>>(){}.type
464476
)
465477
it.resume(
466-
convert?.invoke(map) ?: map as T
478+
converter?.invoke(map) ?: map as T
467479
)
468480
}
469481
})

templates/android/library/src/main/java/io/appwrite/services/ServiceTemplate.kt.twig

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) {
114114
{{ method.headers|map((header, key) => " \"#{key}\" to \"#{header}\"")|join(',\n')|raw }}
115115
)
116116
{% if method.responseModel %}
117-
val convert: (Map<String, Any>) -> {{ _self.resultType(sdk.namespace, method) }} = {
117+
val converter: (Map<String, Any>) -> {{ _self.resultType(sdk.namespace, method) }} = {
118118
{% if method.responseModel == 'any' %}
119119
it
120120
{% else %}
@@ -123,20 +123,26 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) {
123123
}
124124
{% endif %}
125125
{% if 'multipart/form-data' in method.consumes %}
126+
val idParamName: String? = {% if method.parameters.all | filter(p => p.isUploadID) | length > 0 %}{% for parameter in method.parameters.all | filter(parameter => parameter.isUploadID) %}"{{ parameter.name }}"{% endfor %}{% else %}null{% endif %}
127+
126128
{% for parameter in method.parameters.all %}
127129
{% if parameter.type == 'file' %}
128130
val paramName = "{{ parameter.name }}"
129131
{% endif %}
132+
{% if parameter.isUploadID %}
133+
idParamName = "{{ parameter.name }}"
134+
{% endif %}
130135
{% endfor %}
131136
return client.chunkedUpload(
132137
path,
133138
headers,
134139
params,
135140
responseType = {{ _self.resultType(sdk.namespace, method) }}::class.java,
136141
{% if method.responseModel %}
137-
convert = convert,
142+
converter,
138143
{% endif %}
139144
paramName,
145+
idParamName,
140146
onProgress,
141147
)
142148
{% else %}
@@ -147,7 +153,7 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) {
147153
params,
148154
responseType = {{ _self.resultType(sdk.namespace, method) }}::class.java,
149155
{% if method.responseModel %}
150-
convert = convert,
156+
converter,
151157
{% endif %}
152158
)
153159
{% endif %}

0 commit comments

Comments
 (0)