Skip to content

Commit e4eb64f

Browse files
committed
fix backport
Signed-off-by: alperozturk96 <alper_ozturk@proton.me>
1 parent 3b2443f commit e4eb64f

4 files changed

Lines changed: 389 additions & 170 deletions

File tree

Lines changed: 209 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,244 @@
11
/*
22
* Nextcloud Android Library
33
*
4-
* SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
4+
* SPDX-FileCopyrightText: 2021-2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
56
* SPDX-FileCopyrightText: 2021 Tobias Kaminsky <tobias@kaminsky.me>
67
* SPDX-License-Identifier: MIT
78
*/
89
package com.owncloud.android.lib.resources.files
910

1011
import com.nextcloud.common.NextcloudClient
1112
import com.owncloud.android.AbstractIT
13+
import okhttp3.Interceptor
14+
import okhttp3.Response
15+
import okhttp3.ResponseBody
16+
import okio.Buffer
17+
import okio.BufferedSource
18+
import okio.ForwardingSource
19+
import okio.buffer
20+
import org.junit.Assert.assertEquals
21+
import org.junit.Assert.assertFalse
1222
import org.junit.Assert.assertSame
1323
import org.junit.Assert.assertTrue
1424
import org.junit.Test
1525
import java.io.File
1626

27+
@Suppress("Detekt.MagicNumber")
1728
class DownloadFileRemoteOperationIT : AbstractIT() {
29+
private val cacheDir get() = context.externalCacheDir?.absolutePath
30+
1831
@Test
1932
fun download() {
2033
val filePath = createFile("download")
2134
val remotePath = "/download.jpg"
2235
assertTrue(
23-
@Suppress("Detekt.MagicNumber")
2436
UploadFileRemoteOperation(filePath, remotePath, "image/jpg", 1464818400)
2537
.execute(client)
2638
.isSuccess
2739
)
2840

2941
assertTrue(
30-
DownloadFileRemoteOperation(remotePath, context.externalCacheDir?.absolutePath)
42+
DownloadFileRemoteOperation(remotePath, cacheDir)
3143
.execute(nextcloudClient)
3244
.isSuccess
3345
)
3446

3547
val oldFile = File(filePath)
36-
val newFile = File(context.externalCacheDir?.absolutePath + remotePath)
48+
val newFile = File(cacheDir + remotePath)
3749
assertSame(oldFile.length(), newFile.length())
3850
}
51+
52+
@Test
53+
fun downloadLargeFile() {
54+
val filePath = createFile("large_download", 1000)
55+
val remotePath = "/large_download.txt"
56+
assertTrue(
57+
UploadFileRemoteOperation(filePath, remotePath, "text/plain", RANDOM_MTIME)
58+
.execute(client)
59+
.isSuccess
60+
)
61+
62+
assertTrue(
63+
DownloadFileRemoteOperation(remotePath, cacheDir)
64+
.execute(nextcloudClient)
65+
.isSuccess
66+
)
67+
68+
val originalFile = File(filePath)
69+
val downloadedFile = File(cacheDir + remotePath)
70+
assertEquals(originalFile.length(), downloadedFile.length())
71+
}
72+
73+
@Test
74+
fun downloadNonExistentFile() {
75+
val result =
76+
DownloadFileRemoteOperation("/nonexistent_file_12345.txt", cacheDir)
77+
.execute(nextcloudClient)
78+
79+
assertFalse(result.isSuccess)
80+
}
81+
82+
@Test
83+
fun downloadAndVerifyMetadata() {
84+
val filePath = createFile("metadata_download")
85+
val remotePath = "/metadata_download.jpg"
86+
assertTrue(
87+
UploadFileRemoteOperation(filePath, remotePath, "image/jpg", RANDOM_MTIME)
88+
.execute(client)
89+
.isSuccess
90+
)
91+
92+
val operation = DownloadFileRemoteOperation(remotePath, cacheDir)
93+
assertTrue(operation.execute(nextcloudClient).isSuccess)
94+
95+
assertTrue("ETag should not be empty after download", operation.etag.isNotEmpty())
96+
assertTrue("Modification timestamp should be positive after download", operation.modificationTimestamp > 0)
97+
}
98+
99+
@Test
100+
fun downloadMultipleFiles() {
101+
val filePath1 = createFile("multi_download1")
102+
val remotePath1 = "/multi_download1.jpg"
103+
val filePath2 = createFile("multi_download2")
104+
val remotePath2 = "/multi_download2.jpg"
105+
106+
assertTrue(
107+
UploadFileRemoteOperation(filePath1, remotePath1, "image/jpg", RANDOM_MTIME)
108+
.execute(client)
109+
.isSuccess
110+
)
111+
assertTrue(
112+
UploadFileRemoteOperation(filePath2, remotePath2, "image/jpg", RANDOM_MTIME)
113+
.execute(client)
114+
.isSuccess
115+
)
116+
117+
assertTrue(
118+
DownloadFileRemoteOperation(remotePath1, cacheDir)
119+
.execute(nextcloudClient)
120+
.isSuccess
121+
)
122+
assertTrue(
123+
DownloadFileRemoteOperation(remotePath2, cacheDir)
124+
.execute(nextcloudClient)
125+
.isSuccess
126+
)
127+
128+
val downloaded1 = File(cacheDir + remotePath1)
129+
val downloaded2 = File(cacheDir + remotePath2)
130+
assertTrue(downloaded1.exists())
131+
assertTrue(downloaded2.exists())
132+
assertEquals(File(filePath1).length(), downloaded1.length())
133+
assertEquals(File(filePath2).length(), downloaded2.length())
134+
}
135+
136+
@Test
137+
fun downloadAndVerifyContent() {
138+
val filePath = createFile("content_download", 50)
139+
val remotePath = "/content_download.txt"
140+
assertTrue(
141+
UploadFileRemoteOperation(filePath, remotePath, "text/plain", RANDOM_MTIME)
142+
.execute(client)
143+
.isSuccess
144+
)
145+
146+
assertTrue(
147+
DownloadFileRemoteOperation(remotePath, cacheDir)
148+
.execute(nextcloudClient)
149+
.isSuccess
150+
)
151+
152+
val originalFile = File(filePath)
153+
val downloadedFile = File(cacheDir + remotePath)
154+
assertTrue(downloadedFile.exists())
155+
assertTrue(originalFile.readBytes().contentEquals(downloadedFile.readBytes()))
156+
}
157+
158+
@Test
159+
fun downloadedFileExistsAtExpectedPath() {
160+
val filePath = createFile("path_check")
161+
val remotePath = "/path_check.jpg"
162+
assertTrue(
163+
UploadFileRemoteOperation(filePath, remotePath, "image/jpg", RANDOM_MTIME)
164+
.execute(client)
165+
.isSuccess
166+
)
167+
168+
assertTrue(
169+
DownloadFileRemoteOperation(remotePath, cacheDir)
170+
.execute(nextcloudClient)
171+
.isSuccess
172+
)
173+
174+
val expectedFile = File(cacheDir + remotePath)
175+
assertTrue("Downloaded file should exist at expected path", expectedFile.exists())
176+
assertTrue("Downloaded file should not be empty", expectedFile.length() >= 0)
177+
}
178+
179+
@Test
180+
fun downloadLargeFileSucceedsWithNoCallTimeout() {
181+
val filePath = createFile("large_no_call_timeout", 1000)
182+
val remotePath = "/large_no_call_timeout.txt"
183+
assertTrue(
184+
UploadFileRemoteOperation(filePath, remotePath, "text/plain", RANDOM_MTIME)
185+
.execute(client)
186+
.isSuccess
187+
)
188+
189+
val slowOkHttpClient =
190+
nextcloudClient.client
191+
.newBuilder()
192+
.addInterceptor(ChunkDelayInterceptor(delayMs = 100))
193+
.build()
194+
val slowNextcloudClient =
195+
NextcloudClient(
196+
url,
197+
nextcloudClient.getUserIdPlain(),
198+
nextcloudClient.credentials,
199+
slowOkHttpClient,
200+
nextcloudClient.context
201+
)
202+
203+
assertTrue(
204+
DownloadFileRemoteOperation(remotePath, cacheDir)
205+
.execute(slowNextcloudClient)
206+
.isSuccess
207+
)
208+
209+
assertEquals(File(filePath).length(), File(cacheDir + remotePath).length())
210+
}
211+
212+
/**
213+
* Used for create delay for test
214+
*/
215+
private class ChunkDelayInterceptor(
216+
private val delayMs: Long
217+
) : Interceptor {
218+
override fun intercept(chain: Interceptor.Chain): Response {
219+
val response = chain.proceed(chain.request())
220+
val body = response.body
221+
val slowSource =
222+
object : ForwardingSource(body.source()) {
223+
override fun read(
224+
sink: Buffer,
225+
byteCount: Long
226+
): Long {
227+
Thread.sleep(delayMs)
228+
return super.read(sink, byteCount)
229+
}
230+
}
231+
val slowBody =
232+
object : ResponseBody() {
233+
private val bufferedSource: BufferedSource = slowSource.buffer()
234+
235+
override fun contentType() = body.contentType()
236+
237+
override fun contentLength() = body.contentLength()
238+
239+
override fun source() = bufferedSource
240+
}
241+
return response.newBuilder().body(slowBody).build()
242+
}
243+
}
39244
}

library/src/main/java/com/nextcloud/common/NextcloudClient.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/*
22
* Nextcloud Android Library
33
*
4-
* SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
4+
* SPDX-FileCopyrightText: 2019-2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
56
* SPDX-FileCopyrightText: 2019-2024 Tobias Kaminsky
67
* SPDX-FileCopyrightText: 2023 Elv1zz <elv1zz.git@gmail.com>
78
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
@@ -207,6 +208,18 @@ class NextcloudClient private constructor(
207208
}
208209
}
209210

211+
fun withSessionTimeOut(sessionTimeOut: SessionTimeOut): NextcloudClient {
212+
val newClient =
213+
client
214+
.newBuilder()
215+
.readTimeout(sessionTimeOut.readTimeOut.toLong(), TimeUnit.MILLISECONDS)
216+
.connectTimeout(sessionTimeOut.connectionTimeOut.toLong(), TimeUnit.MILLISECONDS)
217+
// needed to prevent cancellation, seems like default value not applied
218+
.callTimeout(0, TimeUnit.MILLISECONDS)
219+
.build()
220+
return NextcloudClient(delegate, credentials, newClient, context)
221+
}
222+
210223
fun getUserIdEncoded(): String = delegate.userIdEncoded!!
211224

212225
fun getUserIdPlain(): String = delegate.userId!!

0 commit comments

Comments
 (0)