Skip to content

Commit 803a7c5

Browse files
sentinelwebsentinelweb
authored andcommitted
#496 - fix cancel bug, Cancelled -> Paused state, CanceleationException -> paused state, download wit transferring prefix
1 parent 8130c97 commit 803a7c5

File tree

27 files changed

+212
-116
lines changed

27 files changed

+212
-116
lines changed

app/src/main/java/uk/co/sentinelweb/cuer/app/util/wrapper/ResourceWrapper.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import androidx.annotation.*
1212
import androidx.core.content.ContextCompat
1313
import androidx.core.graphics.drawable.DrawableCompat
1414
import com.google.android.material.shape.ShapeAppearanceModel
15-
import uk.co.sentinelweb.cuer.app.ui.common.resources.CureColorResources
15+
import uk.co.sentinelweb.cuer.app.ui.common.resources.CuerColorResources
1616
import uk.co.sentinelweb.cuer.app.ui.common.resources.Icon
1717
import uk.co.sentinelweb.cuer.app.ui.common.resources.StringDecoder
1818
import uk.co.sentinelweb.cuer.app.ui.common.resources.StringResource
@@ -44,14 +44,14 @@ class ResourceWrapper constructor(
4444
fun getColor(@ColorRes id: Int) = ContextCompat.getColor(context, id)
4545

4646
@ColorInt
47-
fun getColor(color: CureColorResources): Int {
47+
fun getColor(color: CuerColorResources): Int {
4848
val id = getColorResourceId(color)
4949
return ContextCompat.getColor(context, id)
5050
}
5151

5252
// todo check what happen when id isnt found
5353
@ColorRes
54-
fun getColorResourceId(color: CureColorResources) =
54+
fun getColorResourceId(color: CuerColorResources) =
5555
resources.getIdentifier(color.name, "color", context.getPackageName())
5656
.takeIf { it != 0 }
5757
?: throw IllegalArgumentException("Color doesn't exist : ${color.name}")

domain/src/androidMain/kotlin/uk/co/sentinelweb/cuer/app/db/repository/file/PlatformFileOperation.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ actual class PlatformFileOperation {
3131
File(from.path).copyTo(File(to.path), overwrite = true)
3232
}
3333

34+
actual fun rename(from: AFile, to: AFile) {
35+
File(from.path).renameTo(File(to.path))
36+
}
37+
3438
actual fun list(dir: AFile): List<AFile>? =
3539
File(dir.path).listFiles()
3640
?.map { AFile(it.absolutePath) }

domain/src/commonMain/kotlin/uk/co/sentinelweb/cuer/app/db/repository/file/PlatformFileOperation.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ expect class PlatformFileOperation() {
3131

3232
fun copyTo(from: AFile, to: AFile)
3333

34+
fun rename(from: AFile, to: AFile)
35+
3436
fun list(dir: AFile): List<AFile>?
3537

3638
fun properties(file: AFile): AFileProperties?

domain/src/commonMain/kotlin/uk/co/sentinelweb/cuer/domain/TransferDomain.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ data class TransferDomain(
1414
val progress: Float = 0f, // percent 0..100
1515
val errors: List<ErrorDomain> = listOf()
1616
) : Domain {
17-
enum class Status { Setup, Confirmed, Pending, InProgress, Error, Cancelled, Complete }
17+
enum class Status { Setup, Confirmed, Pending, InProgress, Error, Paused, Complete }
1818
enum class Type { Copy, Delete, /*Move, */ }
1919

2020
@Serializable

domain/src/commonMain/kotlin/uk/co/sentinelweb/cuer/domain/system/ErrorDomain.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package uk.co.sentinelweb.cuer.domain.system
22

33
import kotlinx.serialization.Serializable
4+
import kotlinx.serialization.Transient
45

56
@Serializable
6-
data class ErrorDomain constructor(
7+
data class ErrorDomain(
78
val level: Level = Level.ERROR,
89
val type: Type,
910
val code: Int,
1011
val message: String,
11-
val exception: String? = null
12+
val stackTracce: String? = null,
13+
@Transient
14+
val exception: Throwable? = null,
1215
) {
1316
enum class Type {
1417
HTTP, DATABASE, NETWORK, APP

domain/src/jvmMain/kotlin/uk/co/sentinelweb/cuer/app/db/repository/file/PlatformFileOperation.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ actual class PlatformFileOperation: KoinComponent {
3636
File(from.path).copyTo(File(to.path), overwrite = true)
3737
}
3838

39+
actual fun rename(from: AFile, to: AFile) {
40+
File(from.path).renameTo(File(to.path))
41+
}
42+
3943
actual fun list(dir: AFile): List<AFile>? =
4044
File(dir.path).listFiles()
4145
?.map { AFile(it.absolutePath) }

net/src/commonMain/kotlin/uk/co/sentinelweb/cuer/net/NetModuleConfig.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,9 @@ package uk.co.sentinelweb.cuer.net
22

33
data class NetModuleConfig constructor(
44
val debug: Boolean = false,
5-
val timeoutMs: Long = 5000
6-
)
5+
val timeoutMs: Long = 5000,
6+
) {
7+
companion object {
8+
const val TransferringFileSuffix = ".transferring"
9+
}
10+
}

net/src/commonMain/kotlin/uk/co/sentinelweb/cuer/net/client/ServiceExecutor.kt

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ import uk.co.sentinelweb.cuer.core.wrapper.LogWrapper
1515
import uk.co.sentinelweb.cuer.domain.Domain
1616
import uk.co.sentinelweb.cuer.domain.system.ErrorDomain
1717
import uk.co.sentinelweb.cuer.domain.system.ResponseDomain
18+
import uk.co.sentinelweb.cuer.net.NetModuleConfig.Companion.TransferringFileSuffix
1819
import uk.co.sentinelweb.cuer.net.file.PlatformFileStreamOperations
1920
import uk.co.sentinelweb.cuer.net.remote.RemoteFilesInteractor.TransferProgress
2021
import uk.co.sentinelweb.cuer.remote.server.message.ResponseMessage
2122

23+
2224
internal class ServiceExecutor(
2325
private val client: HttpClient,
2426
private val type: ServiceType,
@@ -133,22 +135,29 @@ internal class ServiceExecutor(
133135
resume: Boolean = true,
134136
headers: Map<String, String> = emptyMap(),
135137
progressCallback: (suspend (TransferProgress) -> Unit)? = null
136-
): Boolean =
137-
try {
138+
): Boolean {
139+
140+
// Ensure parent directories exist
141+
(fileOperation.parent(destinationFile) ?: error("Need a full path parent directory"))
142+
.let { parent -> fileOperation.mkdirs(parent) }
143+
144+
val transferFile = AFile(destinationFile.path + TransferringFileSuffix)
145+
146+
return try {
138147
log.d("downloadFile: url=$url resume=$resume")
139-
// Ensure parent directories exist
140-
fileOperation.parent(destinationFile)
141-
?.let { parent -> fileOperation.mkdirs(parent) }
142148

143149
// Check if we can resume a previous download
144-
val fileExists = fileOperation.exists(destinationFile)
145-
val fileSize = if (fileExists) fileOperation.properties(destinationFile)?.length ?: 0L else 0L
150+
val fileExists = fileOperation.exists(transferFile)
151+
val fileSize = if (fileExists) fileOperation.properties(transferFile)?.length ?: 0L else 0L
146152

147153
log.d("downloadFile: url=$url resume=$resume")
148154
log.d("downloadFile: fileExists=$fileExists fileSize=$fileSize")
149155

150156
// Only try to resume if the file exists and has content
151157
val shouldResume = resume && fileExists && fileSize > 0
158+
if (!shouldResume && fileExists) {
159+
fileOperation.delete(transferFile)
160+
}
152161

153162
// Prepare headers with range for resuming
154163
val requestHeaders = headers.toMutableMap()
@@ -178,15 +187,19 @@ internal class ServiceExecutor(
178187
//log.d("downloadFile.partial: contentLength=$contentLength")
179188
// Open file channel for appending
180189
// todo handle nullability
181-
val fileChannel = fileStreamOperation.openOutputChannel(destinationFile, append = true)
190+
val fileChannel = fileStreamOperation.openOutputChannel(transferFile, append = true)
182191
log.d("fileChannel: $fileChannel")
183-
downloadChunkToFileCatchException(
192+
val result = downloadChunkToFileCatchException(
184193
response,
185194
fileChannel,
186195
fileSize,
187196
totalSize,//if (totalSize > 0) totalSize else fileSize + contentLength,
188197
progressCallback,
189198
)
199+
if (result) {
200+
fileOperation.rename(transferFile, destinationFile)
201+
}
202+
result
190203
}
191204

192205
// Successful response (200) for new or complete downloads
@@ -201,15 +214,19 @@ internal class ServiceExecutor(
201214

202215
// Open file channel for writing (overwrite)
203216
// todo handle nullability
204-
val fileChannel = fileStreamOperation.openOutputChannel(destinationFile, append = false)
217+
val fileChannel = fileStreamOperation.openOutputChannel(transferFile, append = false)
205218
log.d("fileChannel:$fileChannel")
206-
downloadChunkToFileCatchException(
219+
val result = downloadChunkToFileCatchException(
207220
response,
208221
fileChannel,
209222
0L,
210223
contentLength,
211224
progressCallback,
212225
)
226+
if (result) {
227+
fileOperation.rename(transferFile, destinationFile)
228+
}
229+
result
213230
}
214231

215232
response.status == HttpStatusCode.RequestedRangeNotSatisfiable && fileSize > 0 -> {
@@ -235,7 +252,7 @@ internal class ServiceExecutor(
235252
}
236253
}
237254
} catch (e: Exception) {
238-
log.e("Error downloading file from $url to\n ${destinationFile.path}", e)
255+
log.e("Error downloading file from $url to\n ${transferFile.path}", e)
239256
progressCallback?.invoke(
240257
TransferProgress(
241258
progress = -1f,
@@ -247,11 +264,12 @@ internal class ServiceExecutor(
247264
)
248265
)
249266
// Don't delete partial file if we were not trying to resume
250-
if (!resume && fileOperation.exists(destinationFile)) {
251-
fileOperation.delete(destinationFile)
267+
if (!resume && fileOperation.exists(transferFile)) {
268+
fileOperation.delete(transferFile)
252269
}
253270
false
254271
}
272+
}
255273

256274
private suspend fun downloadChunkToFileCatchException(
257275
response: HttpResponse,
@@ -280,6 +298,7 @@ internal class ServiceExecutor(
280298
type = ErrorDomain.Type.HTTP,
281299
message = e.message ?: "Error downloading file",
282300
code = -1,
301+
exception = e,
283302
)
284303
)
285304
)
@@ -288,6 +307,7 @@ internal class ServiceExecutor(
288307
fileChannel?.close(null)
289308
}
290309

310+
291311
/**
292312
* Helper function to download a chunk of data from a ByteReadChannel to a ByteWriteChannel
293313
*/

remote/src/jvmAndAndroid/kotlin/uk/co/sentinelweb/cuer/remote/server/controller/TransfersController.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ class TransfersController(
6161
)
6262
)
6363
}
64-
if (transfer?.status == Cancelled) {
64+
if (transfer?.status == Paused) {
6565
transfer
66-
.also { transferService.cancelInProgressTransfer(it.id) }
66+
.also { transferService.pauseInProgressTransfer(it.id) }
6767
.also {
6868
val cancelledTransfer = transferService.queue
6969
.find { it.id == transfer.id }

remote/src/jvmAndAndroid/kotlin/uk/co/sentinelweb/cuer/remote/server/error/error.kt

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,42 @@ package uk.co.sentinelweb.cuer.remote.server.error
33
import io.ktor.http.*
44
import io.ktor.server.application.*
55
import io.ktor.server.response.*
6+
import uk.co.sentinelweb.cuer.app.ui.local.LocalComposables.getKoin
7+
import uk.co.sentinelweb.cuer.domain.BuildConfigDomain
68
import uk.co.sentinelweb.cuer.domain.ext.serialise
79
import uk.co.sentinelweb.cuer.domain.system.ErrorDomain
810
import uk.co.sentinelweb.cuer.domain.system.ResponseDomain
911
import java.io.PrintWriter
1012
import java.io.StringWriter
1113

14+
private val isDebug by lazy { getKoin().get<BuildConfigDomain>().isDebug }
15+
1216
suspend fun ApplicationCall.error(
1317
status: HttpStatusCode = HttpStatusCode.InternalServerError,
1418
message: String? = null,
1519
error: Throwable? = null
1620
) {
17-
val errorString = error?.let { e ->
18-
StringWriter()
19-
.apply { e.printStackTrace(PrintWriter(this)) }
20-
.toString()
21-
}
21+
22+
val traceString = error
23+
?.takeIf { isDebug }
24+
?.let { e ->
25+
StringWriter()
26+
.apply { e.printStackTrace(PrintWriter(this)) }
27+
.toString()
28+
}
29+
2230
val messageFull = status.description + " : " + (message ?: error?.message ?: "")
2331
response.status(status)
32+
2433
respondText(
2534
ResponseDomain(
2635
ErrorDomain(
2736
level = ErrorDomain.Level.ERROR,
2837
type = ErrorDomain.Type.HTTP,
2938
code = status.value,
3039
message = messageFull,
31-
exception = errorString
40+
stackTracce = traceString,
41+
exception = error
3242
)
3343
).serialise(),
3444
ContentType.Application.Json

0 commit comments

Comments
 (0)