Skip to content

Commit 20a84b5

Browse files
Test quota
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
1 parent d79bc93 commit 20a84b5

File tree

5 files changed

+209
-0
lines changed

5 files changed

+209
-0
lines changed

.drone.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ services:
175175
- su www-data -c "OC_PASS=test php /var/www/html/occ user:add --password-from-env --display-name='Test@Test' test@test"
176176
- su www-data -c "OC_PASS=test php /var/www/html/occ user:add --password-from-env --display-name='Test Spaces' 'test test'"
177177
- su www-data -c "php /var/www/html/occ user:setting user2 files quota 1G"
178+
- su www-data -c "php /var/www/html/occ user:setting user3 files quota 1M"
178179
- su www-data -c "php /var/www/html/occ group:add users"
179180
- su www-data -c "php /var/www/html/occ group:adduser users user1"
180181
- su www-data -c "php /var/www/html/occ group:adduser users user2"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Nextcloud Android client application
3+
*
4+
* @author Tobias Kaminsky
5+
* Copyright (C) 2023 Tobias Kaminsky
6+
* Copyright (C) 2023 Nextcloud GmbH
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Affero General Public License as published by
10+
* the Free Software Foundation, either version 3 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU Affero General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Affero General Public License
19+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
20+
*
21+
*/
22+
23+
package com.owncloud.android.lib.resources.files
24+
25+
import com.owncloud.android.AbstractIT
26+
import com.owncloud.android.lib.common.OwnCloudBasicCredentials
27+
import com.owncloud.android.lib.common.OwnCloudClientFactory
28+
import org.junit.Assert.assertFalse
29+
import org.junit.Assert.assertTrue
30+
import org.junit.Test
31+
32+
class CheckEnoughQuotaRemoteOperationIT : AbstractIT() {
33+
@Test
34+
fun enoughQuota() {
35+
val sut = CheckEnoughQuotaRemoteOperation("/", LARGE_FILE).execute(client)
36+
assertTrue(sut.isSuccess)
37+
}
38+
39+
@Test
40+
fun noQuota() {
41+
// user3 has only 1M quota
42+
val client3 = OwnCloudClientFactory.createOwnCloudClient(url, context, true)
43+
client3.credentials = OwnCloudBasicCredentials("user3", "user3")
44+
val sut = CheckEnoughQuotaRemoteOperation("/", LARGE_FILE).execute(client3)
45+
assertFalse(sut.isSuccess)
46+
}
47+
48+
companion object {
49+
const val LARGE_FILE = 5 * 1024 * 1024L
50+
}
51+
}

library/src/androidTest/java/com/owncloud/android/lib/resources/files/UploadFileRemoteOperationIT.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@ package com.owncloud.android.lib.resources.files
2323

2424
import android.os.Build
2525
import com.owncloud.android.AbstractIT
26+
import com.owncloud.android.lib.common.OwnCloudBasicCredentials
27+
import com.owncloud.android.lib.common.OwnCloudClientFactory
28+
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
2629
import com.owncloud.android.lib.common.utils.Log_OC
2730
import com.owncloud.android.lib.resources.files.model.RemoteFile
2831
import junit.framework.TestCase.assertEquals
32+
import org.junit.Assert.assertFalse
2933
import org.junit.Assert.assertNotNull
3034
import org.junit.Assert.assertTrue
3135
import org.junit.Test
@@ -94,6 +98,35 @@ class UploadFileRemoteOperationIT : AbstractIT() {
9498
)
9599
}
96100

101+
@Throws(Throwable::class)
102+
@Test
103+
fun uploadFileWithQuotaExceeded() {
104+
// user3 has quota of 1Mb
105+
val client3 = OwnCloudClientFactory.createOwnCloudClient(url, context, true)
106+
client3.credentials = OwnCloudBasicCredentials("user3", "user3")
107+
client3.userId = "user3"
108+
109+
// create file
110+
val filePath = createFile("quota", LARGE_FILE)
111+
val remotePath = "/quota.md"
112+
113+
val creationTimestamp = getCreationTimestamp(File(filePath))
114+
val sut =
115+
UploadFileRemoteOperation(
116+
filePath,
117+
remotePath,
118+
"text/markdown",
119+
"",
120+
RANDOM_MTIME,
121+
creationTimestamp,
122+
true
123+
)
124+
125+
val uploadResult = sut.execute(client3)
126+
assertFalse(uploadResult.isSuccess)
127+
assertEquals(ResultCode.QUOTA_EXCEEDED, uploadResult.code)
128+
}
129+
97130
private fun getCreationTimestamp(file: File): Long? {
98131
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
99132
return null
@@ -114,5 +147,6 @@ class UploadFileRemoteOperationIT : AbstractIT() {
114147

115148
companion object {
116149
const val TIME_OFFSET = 10
150+
const val LARGE_FILE = 10 * 1024
117151
}
118152
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/* Nextcloud Android Library is available under MIT license
2+
*
3+
* @author Tobias Kaminsky
4+
* Copyright (C) 2023 Tobias Kaminsky
5+
* Copyright (C) 2023 Nextcloud GmbH
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21+
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22+
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*
26+
*/
27+
package com.owncloud.android.lib.resources.files
28+
29+
import com.owncloud.android.lib.common.OwnCloudClient
30+
import com.owncloud.android.lib.common.network.WebdavEntry
31+
import com.owncloud.android.lib.common.operations.RemoteOperation
32+
import com.owncloud.android.lib.common.operations.RemoteOperationResult
33+
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
34+
import com.owncloud.android.lib.common.utils.Log_OC
35+
import org.apache.commons.httpclient.HttpStatus
36+
import org.apache.jackrabbit.webdav.DavException
37+
import org.apache.jackrabbit.webdav.client.methods.PropFindMethod
38+
import org.apache.jackrabbit.webdav.property.DavPropertyName
39+
import org.apache.jackrabbit.webdav.property.DavPropertyNameSet
40+
import java.io.File
41+
import java.io.IOException
42+
43+
/**
44+
* Check if remaining quota is big enough
45+
* @param fileSize filesize in bytes
46+
*/
47+
class CheckEnoughQuotaRemoteOperation(val path: String, private val fileSize: Long) :
48+
RemoteOperation<Boolean>() {
49+
@Deprecated("Deprecated in Java")
50+
@Suppress("Detekt.ReturnCount")
51+
override fun run(client: OwnCloudClient): RemoteOperationResult<Boolean> {
52+
var propfind: PropFindMethod? = null
53+
try {
54+
val file = File(path)
55+
val folder =
56+
if (file.path.endsWith(FileUtils.PATH_SEPARATOR)) {
57+
file.path
58+
} else {
59+
file.parent ?: throw IllegalStateException("Parent path not found")
60+
}
61+
62+
val propSet = DavPropertyNameSet()
63+
propSet.add(QUOTA_PROPERTY)
64+
propfind =
65+
PropFindMethod(
66+
client.getFilesDavUri(folder),
67+
propSet,
68+
0
69+
)
70+
val status = client.executeMethod(propfind, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT)
71+
if (status == HttpStatus.SC_MULTI_STATUS || status == HttpStatus.SC_OK) {
72+
val resp = propfind.responseBodyAsMultiStatus.responses[0]
73+
val string = resp.getProperties(HttpStatus.SC_OK)[QUOTA_PROPERTY].value as String
74+
val quota = string.toLong()
75+
return if (isSuccess(quota)) {
76+
RemoteOperationResult<Boolean>(true, propfind)
77+
} else {
78+
RemoteOperationResult<Boolean>(false, propfind)
79+
}
80+
}
81+
if (status == HttpStatus.SC_NOT_FOUND) {
82+
return RemoteOperationResult(ResultCode.FILE_NOT_FOUND)
83+
}
84+
} catch (e: DavException) {
85+
Log_OC.e(TAG, "Error while retrieving quota")
86+
} catch (e: IOException) {
87+
Log_OC.e(TAG, "Error while retrieving quota")
88+
} catch (e: NumberFormatException) {
89+
Log_OC.e(TAG, "Error while retrieving quota")
90+
} finally {
91+
propfind?.releaseConnection()
92+
}
93+
return RemoteOperationResult(ResultCode.ETAG_CHANGED)
94+
}
95+
96+
private fun isSuccess(quota: Long): Boolean {
97+
return quota >= fileSize ||
98+
quota == UNKNOWN_FREE_SPACE ||
99+
quota == UNCOMPUTED_FREE_SPACE ||
100+
quota == UNLIMITED_FREE_SPACE
101+
}
102+
103+
companion object {
104+
private const val SYNC_READ_TIMEOUT = 40000
105+
private const val SYNC_CONNECTION_TIMEOUT = 5000
106+
private const val UNCOMPUTED_FREE_SPACE = -1L
107+
private const val UNKNOWN_FREE_SPACE = -2L
108+
private const val UNLIMITED_FREE_SPACE = -3L
109+
private val QUOTA_PROPERTY = DavPropertyName.create(WebdavEntry.PROPERTY_QUOTA_AVAILABLE_BYTES)
110+
private val TAG = CheckEnoughQuotaRemoteOperation::class.java.simpleName
111+
}
112+
}

library/src/main/java/com/owncloud/android/lib/resources/files/UploadFileRemoteOperation.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,17 @@ public UploadFileRemoteOperation(String localPath,
152152
@Override
153153
protected RemoteOperationResult<String> run(OwnCloudClient client) {
154154
RemoteOperationResult<String> result;
155+
156+
// check quota
157+
long fileLength = new File(localPath).length();
158+
RemoteOperationResult checkEnoughQuotaResult =
159+
new CheckEnoughQuotaRemoteOperation(remotePath, fileLength)
160+
.run(client);
161+
162+
if (!checkEnoughQuotaResult.isSuccess()) {
163+
return new RemoteOperationResult<>(checkEnoughQuotaResult.getCode());
164+
}
165+
155166
DefaultHttpMethodRetryHandler oldRetryHandler =
156167
(DefaultHttpMethodRetryHandler) client.getParams().getParameter(HttpMethodParams.RETRY_HANDLER);
157168

0 commit comments

Comments
 (0)