Skip to content

Commit 46e8b64

Browse files
committed
resumable upload support
1 parent 04867a0 commit 46e8b64

File tree

12 files changed

+240
-224
lines changed

12 files changed

+240
-224
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ repositories {
3939
Next, add the dependency to your project's `build.gradle(.kts)` file:
4040

4141
```groovy
42-
implementation("io.appwrite:sdk-for-kotlin:0.3.0")
42+
implementation("io.appwrite:sdk-for-kotlin:0.4.0")
4343
```
4444

4545
### Maven
@@ -50,7 +50,7 @@ Add this to your project's `pom.xml` file:
5050
<dependency>
5151
<groupId>io.appwrite</groupId>
5252
<artifactId>sdk-for-kotlin</artifactId>
53-
<version>0.3.0</version>
53+
<version>0.4.0</version>
5454
</dependency>
5555
</dependencies>
5656
```

build.gradle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
plugins {
2-
id "org.jetbrains.kotlin.jvm" version '1.5.31'
2+
id "org.jetbrains.kotlin.jvm" version '1.6.10'
33
id "java-library"
44
id "io.github.gradle-nexus.publish-plugin" version "1.1.0"
55
}
@@ -29,12 +29,12 @@ repositories {
2929
}
3030

3131
dependencies {
32-
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
33-
api(platform("com.squareup.okhttp3:okhttp-bom:4.9.0"))
32+
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
33+
api(platform("com.squareup.okhttp3:okhttp-bom:4.9.3"))
3434
api("com.squareup.okhttp3:okhttp")
3535
implementation("com.squareup.okhttp3:okhttp-urlconnection")
3636
implementation("com.squareup.okhttp3:logging-interceptor")
37-
implementation("com.google.code.gson:gson:2.8.7")
37+
implementation("com.google.code.gson:gson:2.9.0")
3838

3939
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit'
4040
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists

src/main/kotlin/io/appwrite/Client.kt

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import okhttp3.logging.HttpLoggingInterceptor
2020
import java.io.BufferedInputStream
2121
import java.io.BufferedReader
2222
import java.io.File
23+
import java.io.RandomAccessFile
2324
import java.io.IOException
2425
import java.security.SecureRandom
2526
import java.security.cert.X509Certificate
@@ -219,7 +220,7 @@ class Client @JvmOverloads constructor(
219220
headers: Map<String, String> = mapOf(),
220221
params: Map<String, Any?> = mapOf(),
221222
responseType: Class<T>,
222-
convert: ((Map<String, Any,>) -> T)? = null
223+
converter: ((Map<String, Any,>) -> T)? = null
223224
): T {
224225
val filteredParams = params.filterValues { it != null }
225226

@@ -255,7 +256,7 @@ class Client @JvmOverloads constructor(
255256
.get()
256257
.build()
257258

258-
return awaitResponse(request, responseType, convert)
259+
return awaitResponse(request, responseType, converter)
259260
}
260261

261262
val body = if (MultipartBody.FORM.toString() == headers["content-type"]) {
@@ -292,7 +293,7 @@ class Client @JvmOverloads constructor(
292293
.method(method, body)
293294
.build()
294295

295-
return awaitResponse(request, responseType, convert)
296+
return awaitResponse(request, responseType, converter)
296297
}
297298

298299
/**
@@ -310,8 +311,9 @@ class Client @JvmOverloads constructor(
310311
headers: MutableMap<String, String>,
311312
params: MutableMap<String, Any?>,
312313
responseType: Class<T>,
313-
convert: ((Map<String, Any,>) -> T),
314+
converter: ((Map<String, Any,>) -> T),
314315
paramName: String,
316+
idParamName: String? = null,
315317
onProgress: ((UploadProgress) -> Unit)? = null,
316318
): T {
317319
val file = params[paramName] as File
@@ -324,74 +326,84 @@ class Client @JvmOverloads constructor(
324326
file.asRequestBody()
325327
)
326328
return call(
327-
"POST",
329+
method = "POST",
328330
path,
329331
headers,
330332
params,
331333
responseType,
332-
convert
334+
converter
333335
)
334336
}
335337

336-
val input = file.inputStream().buffered()
338+
val input = RandomAccessFile(file, "r")
337339
val buffer = ByteArray(CHUNK_SIZE)
338340
var offset = 0L
339341
var result: Map<*, *>? = null
340342

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

356366
headers["Content-Range"] =
357367
"bytes $offset-${((offset + CHUNK_SIZE) - 1).coerceAtMost(size)}/$size"
358368

359369
result = call(
360-
"POST",
370+
method = "POST",
361371
path,
362372
headers,
363373
params,
364-
Map::class.java
374+
responseType = Map::class.java
365375
)
366376

367377
offset += CHUNK_SIZE
368378
headers["x-appwrite-id"] = result!!["\$id"].toString()
369-
onProgress?.invoke(UploadProgress(
370-
id = result!!["\$id"].toString(),
371-
progress = offset.coerceAtMost(size).toDouble()/size * 100,
372-
sizeUploaded = offset.coerceAtMost(size),
373-
chunksTotal = result!!["chunkTotal"].toString().toInt(),
374-
chunksUploaded = result!!["chunkUploaded"].toString().toInt(),
375-
))
379+
onProgress?.invoke(
380+
UploadProgress(
381+
id = result!!["\$id"].toString(),
382+
progress = offset.coerceAtMost(size).toDouble() / size * 100,
383+
sizeUploaded = offset.coerceAtMost(size),
384+
chunksTotal = result!!["chunksTotal"].toString().toInt(),
385+
chunksUploaded = result!!["chunksUploaded"].toString().toInt(),
386+
)
387+
)
376388
}
377389

378-
return convert(result as Map<String, Any>)
390+
return converter(result as Map<String, Any>)
379391
}
380392

381393
/**
382394
* Await Response
383395
*
384396
* @param request
385397
* @param responseType
386-
* @param convert
398+
* @param converter
387399
*
388400
* @return [T]
389401
*/
390402
@Throws(AppwriteException::class)
391403
private suspend fun <T> awaitResponse(
392404
request: Request,
393405
responseType: Class<T>,
394-
convert: ((Map<String, Any,>) -> T)? = null
406+
converter: ((Map<String, Any,>) -> T)? = null
395407
) = suspendCancellableCoroutine<T> {
396408
http.newCall(request).enqueue(object : Callback {
397409
override fun onFailure(call: Call, e: IOException) {
@@ -457,7 +469,7 @@ class Client @JvmOverloads constructor(
457469
object : TypeToken<Map<String, Any>>(){}.type
458470
)
459471
it.resume(
460-
convert?.invoke(map) ?: map as T
472+
converter?.invoke(map) ?: map as T
461473
)
462474
}
463475
})

0 commit comments

Comments
 (0)