1
0
mirror of https://github.com/owncloud/android-library.git synced 2025-06-07 16:06:08 +00:00

Compare commits

..

No commits in common. "master" and "1.0.14" have entirely different histories.

132 changed files with 3643 additions and 4354 deletions

View File

@ -6,5 +6,5 @@ jobs:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1

View File

@ -49,6 +49,6 @@ ownCloud Android Library uses OkHttp version 4.6.0, licensed under Apache Licens
### Compatibility
ownCloud Android Library is valid for Android systems from version Android 6 (android:minSdkVersion="23" android:targetSdkVersion="33").
ownCloud Android Library is valid for Android systems from version Android 5 (android:minSdkVersion="21" android:targetSdkVersion="29").
ownCloud Android library supports ownCloud server from version 4.5.

View File

@ -1,7 +1,7 @@
buildscript {
ext {
orgJetbrainsKotlin = '1.8.10'
comSquareupMoshi = '1.14.0'
kotlinVersion = '1.6.10'
moshiVersion = "1.13.0"
}
repositories {
@ -10,16 +10,12 @@ buildscript {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath "org.jlleitschuh.gradle:ktlint-gradle:11.1.0"
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$orgJetbrainsKotlin"
classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.1"
classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
plugins {
id 'com.google.devtools.ksp' version '1.8.10-1.0.9' apply false
}
allprojects {
repositories {
google()
@ -30,5 +26,4 @@ allprojects {
subprojects {
apply plugin: "org.jlleitschuh.gradle.ktlint"
apply plugin: "com.google.devtools.ksp"
}
}

View File

@ -1,15 +0,0 @@
#!/bin/bash
check_license_in_file() {
if ! head -n 20 $FILE | grep -q "Permission is hereby granted, free of charge, to any person obtaining a copy"
then
echo "$FILE does not contain a current copyright header"
fi
}
for FILE in $(find owncloudComLibrary/src -name "*.java" -o -name "*.kt")
do
check_license_in_file
done
./gradlew ktlintFormat

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -1,43 +1,47 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'com.google.devtools.ksp'
apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
dependencies {
api 'com.squareup.okhttp3:okhttp:4.6.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$orgJetbrainsKotlin"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
api 'com.gitlab.ownclouders:dav4android:oc_support_2.1.5'
api 'com.github.AppDevNext.Logcat:LogcatCore:2.2.2'
// Moshi
implementation("com.squareup.moshi:moshi-kotlin:$comSquareupMoshi") {
implementation("com.squareup.moshi:moshi-kotlin:$moshiVersion") {
exclude module: "kotlin-reflect"
}
implementation 'org.apache.commons:commons-lang3:3.12.0'
ksp "com.squareup.moshi:moshi-kotlin-codegen:$comSquareupMoshi"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion"
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.10'
debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
testImplementation 'org.robolectric:robolectric:4.7.3'
}
android {
compileSdkVersion 33
compileSdkVersion 30
defaultConfig {
minSdkVersion 23
targetSdkVersion 33
minSdkVersion 21
targetSdkVersion 30
versionCode = 10001400
versionName = "1.0.14"
}
lint {
lintOptions {
abortOnError false
ignoreWarnings true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
namespace 'com.owncloud.android.lib'
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- ownCloud Android Library is available under MIT license
Copyright (C) 2023 ownCloud GmbH.
Copyright (C) 2016 ownCloud GmbH.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -23,8 +23,16 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest package="com.owncloud.android.lib"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- USE_CREDENTIALS, MANAGE_ACCOUNTS and AUTHENTICATE_ACCOUNTS are needed for API < 23.
In API >= 23 the do not exist anymore -->
<uses-permission
android:name="android.permission.MANAGE_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

View File

@ -1,221 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2016 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common
import android.accounts.AccountManager
import android.accounts.AccountsException
import android.content.Context
import com.owncloud.android.lib.common.authentication.OwnCloudCredentials
import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation
import com.owncloud.android.lib.resources.status.GetRemoteStatusOperation
import com.owncloud.android.lib.resources.status.RemoteServerInfo
import org.apache.commons.lang3.exception.ExceptionUtils
import timber.log.Timber
import java.io.IOException
/**
* ConnectionValidator
*
* @author Christian Schabesberger
*/
class ConnectionValidator(
val context: Context,
private val clearCookiesOnValidation: Boolean
) {
fun validate(baseClient: OwnCloudClient, singleSessionManager: SingleSessionManager, context: Context): Boolean {
try {
var validationRetryCount = 0
val client = OwnCloudClient(baseClient.baseUri, null, false, singleSessionManager, context)
if (clearCookiesOnValidation) {
client.clearCookies()
} else {
client.cookiesForBaseUri = baseClient.cookiesForBaseUri
}
client.account = baseClient.account
client.credentials = baseClient.credentials
while (validationRetryCount < VALIDATION_RETRY_COUNT) {
Timber.d("validationRetryCount %d", validationRetryCount)
var successCounter = 0
var failCounter = 0
client.setFollowRedirects(true)
if (isOwnCloudStatusOk(client)) {
successCounter++
} else {
failCounter++
}
// Skip the part where we try to check if we can access the parts where we have to be logged in... if we are not logged in
if (baseClient.credentials !is OwnCloudAnonymousCredentials) {
client.setFollowRedirects(false)
val contentReply = canAccessRootFolder(client)
if (contentReply.httpCode == HttpConstants.HTTP_OK) {
if (contentReply.data == true) { //if data is true it means that the content reply was ok
successCounter++
} else {
failCounter++
}
} else {
failCounter++
if (contentReply.httpCode == HttpConstants.HTTP_UNAUTHORIZED) {
checkUnauthorizedAccess(client, singleSessionManager, contentReply.httpCode)
}
}
}
if (successCounter >= failCounter) {
baseClient.credentials = client.credentials
baseClient.cookiesForBaseUri = client.cookiesForBaseUri
return true
}
validationRetryCount++
}
Timber.d("Could not authenticate or get valid data from owncloud")
} catch (e: Exception) {
Timber.d(ExceptionUtils.getStackTrace(e))
}
return false
}
private fun isOwnCloudStatusOk(client: OwnCloudClient): Boolean {
val reply = getOwnCloudStatus(client)
// dont check status code. It currently relais on the broken redirect code of the owncloud client
// TODO: Use okhttp redirect and add this check again
// return reply.httpCode == HttpConstants.HTTP_OK &&
return !reply.isException &&
reply.data != null
}
private fun getOwnCloudStatus(client: OwnCloudClient): RemoteOperationResult<RemoteServerInfo> {
val remoteStatusOperation = GetRemoteStatusOperation()
return remoteStatusOperation.execute(client)
}
private fun canAccessRootFolder(client: OwnCloudClient): RemoteOperationResult<Boolean> {
val checkPathExistenceRemoteOperation = CheckPathExistenceRemoteOperation("/", true)
return checkPathExistenceRemoteOperation.execute(client)
}
/**
* Determines if credentials should be invalidated according the to the HTTPS status
* of a network request just performed.
*
* @param httpStatusCode Result of the last request ran with the 'credentials' belows.
* @return 'True' if credentials should and might be invalidated, 'false' if shouldn't or
* cannot be invalidated with the given arguments.
*/
private fun shouldInvalidateAccountCredentials(credentials: OwnCloudCredentials, account: OwnCloudAccount, httpStatusCode: Int): Boolean {
var shouldInvalidateAccountCredentials = httpStatusCode == HttpConstants.HTTP_UNAUTHORIZED
shouldInvalidateAccountCredentials = shouldInvalidateAccountCredentials and // real credentials
(credentials !is OwnCloudAnonymousCredentials)
// test if have all the needed to effectively invalidate ...
shouldInvalidateAccountCredentials =
shouldInvalidateAccountCredentials and (account.savedAccount != null)
Timber.d(
"""Received error: $httpStatusCode,
account: ${account.name}
credentials are real: ${credentials !is OwnCloudAnonymousCredentials},
so we need to invalidate credentials for account ${account.name} : $shouldInvalidateAccountCredentials"""
)
return shouldInvalidateAccountCredentials
}
/**
* Invalidates credentials stored for the given account in the system [AccountManager] and in
* current [SingleSessionManager.getDefaultSingleton] instance.
*
*
* [.shouldInvalidateAccountCredentials] should be called first.
*
*/
private fun invalidateAccountCredentials(account: OwnCloudAccount, credentials: OwnCloudCredentials) {
Timber.i("Invalidating account credentials for account $account")
val am = AccountManager.get(context)
am.invalidateAuthToken(
account.savedAccount.type,
credentials.authToken
)
am.clearPassword(account.savedAccount) // being strict, only needed for Basic Auth credentials
}
/**
* Checks the status code of an execution and decides if should be repeated with fresh credentials.
*
*
* Invalidates current credentials if the request failed as anauthorized.
*
*
* Refresh current credentials if possible, and marks a retry.
*
* @return
*/
private fun checkUnauthorizedAccess(client: OwnCloudClient, singleSessionManager: SingleSessionManager, status: Int): Boolean {
var credentialsWereRefreshed = false
val account = client.account
val credentials = account.credentials
if (shouldInvalidateAccountCredentials(credentials, account, status)) {
invalidateAccountCredentials(account, credentials)
if (credentials.authTokenCanBeRefreshed()) {
try {
// This command does the actual refresh
Timber.i("Trying to refresh auth token for account $account")
account.loadCredentials(context)
// if mAccount.getCredentials().length() == 0 --> refresh failed
client.credentials = account.credentials
credentialsWereRefreshed = true
} catch (e: AccountsException) {
Timber.e(
e, "Error while trying to refresh auth token for %s\ntrace: %s",
account.savedAccount.name,
ExceptionUtils.getStackTrace(e)
)
} catch (e: IOException) {
Timber.e(
e, "Error while trying to refresh auth token for %s\ntrace: %s",
account.savedAccount.name,
ExceptionUtils.getStackTrace(e)
)
}
if (!credentialsWereRefreshed) {
// if credentials are not refreshed, client must be removed
// from the OwnCloudClientManager to prevent it is reused once and again
Timber.w("Credentials were not refreshed, client will be removed from the Session Manager to prevent using it over and over")
singleSessionManager.removeClientFor(account)
}
}
// else: onExecute will finish with status 401
}
return credentialsWereRefreshed
}
companion object {
private const val VALIDATION_RETRY_COUNT = 3
}
}

View File

@ -25,9 +25,11 @@
package com.owncloud.android.lib.common;
import android.content.Context;
import android.accounts.AccountManager;
import android.accounts.AccountsException;
import android.net.Uri;
import at.bitfire.dav4jvm.exception.HttpException;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.authentication.OwnCloudCredentials;
import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory;
@ -35,7 +37,9 @@ import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory
import com.owncloud.android.lib.common.http.HttpClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.HttpBaseMethod;
import com.owncloud.android.lib.common.network.RedirectionPath;
import com.owncloud.android.lib.common.utils.RandomUtils;
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
import timber.log.Timber;
@ -43,56 +47,42 @@ import timber.log.Timber;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import static com.owncloud.android.lib.common.http.HttpConstants.AUTHORIZATION_HEADER;
import static com.owncloud.android.lib.common.http.HttpConstants.HTTP_MOVED_PERMANENTLY;
import static com.owncloud.android.lib.common.http.HttpConstants.OC_X_REQUEST_ID;
public class OwnCloudClient extends HttpClient {
public static final String WEBDAV_FILES_PATH_4_0 = "/remote.php/dav/files/";
public static final String WEBDAV_PATH_4_0_AND_LATER = "/remote.php/dav";
public static final String STATUS_PATH = "/status.php";
private static final String WEBDAV_UPLOADS_PATH_4_0 = "/remote.php/dav/uploads/";
private static final int MAX_RETRY_COUNT = 2;
private static final int MAX_REDIRECTIONS_COUNT = 3;
private static final int MAX_REPEAT_COUNT_WITH_FRESH_CREDENTIALS = 1;
private static byte[] sExhaustBuffer = new byte[1024];
private static int sIntanceCounter = 0;
private OwnCloudCredentials mCredentials = null;
private int mInstanceNumber;
private Uri mBaseUri;
private OwnCloudVersion mVersion = null;
private OwnCloudAccount mAccount;
private final ConnectionValidator mConnectionValidator;
private Object mRequestMutex = new Object();
// If set to true a mutex will be used to prevent parallel execution of the execute() method
// if false the execute() method can be called even though the mutex is already aquired.
// This is used for the ConnectionValidator, which has to be able to execute OperationsWhile all "normal" operations net
// to be set on hold.
private final Boolean mSynchronizeRequests;
private SingleSessionManager mSingleSessionManager = null;
private boolean mFollowRedirects = false;
public OwnCloudClient(Uri baseUri,
ConnectionValidator connectionValidator,
boolean synchronizeRequests,
SingleSessionManager singleSessionManager,
Context context) {
super(context);
private boolean mFollowRedirects;
public OwnCloudClient(Uri baseUri) {
if (baseUri == null) {
throw new IllegalArgumentException("Parameter 'baseUri' cannot be NULL");
}
mBaseUri = baseUri;
mSynchronizeRequests = synchronizeRequests;
mSingleSessionManager = singleSessionManager;
mInstanceNumber = sIntanceCounter++;
Timber.d("#" + mInstanceNumber + "Creating OwnCloudClient");
clearCredentials();
clearCookies();
mConnectionValidator = connectionValidator;
}
public void clearCredentials() {
@ -102,63 +92,117 @@ public class OwnCloudClient extends HttpClient {
}
public int executeHttpMethod(HttpBaseMethod method) throws Exception {
if (mSynchronizeRequests) {
synchronized (mRequestMutex) {
return saveExecuteHttpMethod(method);
}
} else {
return saveExecuteHttpMethod(method);
}
}
private int saveExecuteHttpMethod(HttpBaseMethod method) throws Exception {
boolean repeatWithFreshCredentials;
int repeatCounter = 0;
int status;
if (mFollowRedirects) {
method.setFollowRedirects(true);
}
boolean retry;
do {
repeatCounter++;
retry = false;
String requestId = RandomUtils.generateRandomUUID();
// Header to allow tracing requests in apache and ownCloud logs
Timber.d("Executing in request with id %s", requestId);
method.setRequestHeader(HttpConstants.OC_X_REQUEST_ID, requestId);
method.setRequestHeader(HttpConstants.USER_AGENT_HEADER, SingleSessionManager.getUserAgent());
method.setRequestHeader(HttpConstants.ACCEPT_LANGUAGE_HEADER, Locale.getDefault().getLanguage());
method.setRequestHeader(HttpConstants.ACCEPT_ENCODING_HEADER, HttpConstants.ACCEPT_ENCODING_IDENTITY);
if (mCredentials.getHeaderAuth() != null && !mCredentials.getHeaderAuth().isEmpty()) {
if (mCredentials.getHeaderAuth() != null && method.getRequestHeader(AUTHORIZATION_HEADER) == null) {
method.setRequestHeader(AUTHORIZATION_HEADER, mCredentials.getHeaderAuth());
}
status = method.execute();
status = method.execute(this);
if (shouldConnectionValidatorBeCalled(method, status)) {
retry = mConnectionValidator.validate(this, mSingleSessionManager, getContext()); // retry on success fail on no success
} else if (method.getFollowPermanentRedirects() && status == HTTP_MOVED_PERMANENTLY) {
retry = true;
method.setFollowRedirects(true);
if (mFollowRedirects) {
status = followRedirection(method).getLastStatus();
}
} while (retry && repeatCounter < MAX_RETRY_COUNT);
repeatWithFreshCredentials = checkUnauthorizedAccess(status, repeatCounter);
if (repeatWithFreshCredentials) {
repeatCounter++;
}
} while (repeatWithFreshCredentials);
return status;
}
private boolean shouldConnectionValidatorBeCalled(HttpBaseMethod method, int status) {
private int executeRedirectedHttpMethod(HttpBaseMethod method) throws Exception {
boolean repeatWithFreshCredentials;
int repeatCounter = 0;
int status;
return mConnectionValidator != null && (
(!(mCredentials instanceof OwnCloudAnonymousCredentials) &&
status == HttpConstants.HTTP_UNAUTHORIZED
) || (!mFollowRedirects &&
!method.getFollowRedirects() &&
status == HttpConstants.HTTP_MOVED_TEMPORARILY
)
);
do {
String requestId = RandomUtils.generateRandomUUID();
// Header to allow tracing requests in apache and ownCloud logs
Timber.d("Executing in request with id %s", requestId);
method.setRequestHeader(OC_X_REQUEST_ID, requestId);
method.setRequestHeader(HttpConstants.USER_AGENT_HEADER, SingleSessionManager.getUserAgent());
method.setRequestHeader(HttpConstants.ACCEPT_ENCODING_HEADER, HttpConstants.ACCEPT_ENCODING_IDENTITY);
if (mCredentials.getHeaderAuth() != null) {
method.setRequestHeader(AUTHORIZATION_HEADER, mCredentials.getHeaderAuth());
}
status = method.execute();
repeatWithFreshCredentials = checkUnauthorizedAccess(status, repeatCounter);
if (repeatWithFreshCredentials) {
repeatCounter++;
}
} while (repeatWithFreshCredentials);
return status;
}
public RedirectionPath followRedirection(HttpBaseMethod method) throws Exception {
int redirectionsCount = 0;
int status = method.getStatusCode();
RedirectionPath redirectionPath = new RedirectionPath(status, MAX_REDIRECTIONS_COUNT);
while (redirectionsCount < MAX_REDIRECTIONS_COUNT &&
(status == HttpConstants.HTTP_MOVED_PERMANENTLY ||
status == HttpConstants.HTTP_MOVED_TEMPORARILY ||
status == HttpConstants.HTTP_TEMPORARY_REDIRECT)
) {
final String location = method.getResponseHeader(HttpConstants.LOCATION_HEADER) != null
? method.getResponseHeader(HttpConstants.LOCATION_HEADER)
: method.getResponseHeader(HttpConstants.LOCATION_HEADER_LOWER);
if (location != null) {
Timber.d("#" + mInstanceNumber + "Location to redirect: " + location);
redirectionPath.addLocation(location);
// Release the connection to avoid reach the max number of connections per host
// due to it will be set a different url
exhaustResponse(method.getResponseBodyAsStream());
method.setUrl(HttpUrl.parse(location));
final String destination = method.getRequestHeader("Destination") != null
? method.getRequestHeader("Destination")
: method.getRequestHeader("destination");
if (destination != null) {
final int suffixIndex = location.lastIndexOf(getUserFilesWebDavUri().toString());
final String redirectionBase = location.substring(0, suffixIndex);
final String destinationPath = destination.substring(mBaseUri.toString().length());
method.setRequestHeader("destination", redirectionBase + destinationPath);
}
try {
status = executeRedirectedHttpMethod(method);
} catch (HttpException e) {
if (e.getMessage().contains(Integer.toString(HttpConstants.HTTP_MOVED_TEMPORARILY))) {
status = HttpConstants.HTTP_MOVED_TEMPORARILY;
} else {
throw e;
}
}
redirectionPath.addStatus(status);
redirectionsCount++;
} else {
Timber.d(" #" + mInstanceNumber + "No location to redirect!");
status = HttpConstants.HTTP_NOT_FOUND;
}
}
return redirectionPath;
}
/**
@ -185,7 +229,7 @@ public class OwnCloudClient extends HttpClient {
return (mCredentials instanceof OwnCloudAnonymousCredentials || mAccount == null)
? Uri.parse(mBaseUri + WEBDAV_FILES_PATH_4_0)
: Uri.parse(mBaseUri + WEBDAV_FILES_PATH_4_0 + AccountUtils.getUserId(
mAccount.getSavedAccount(), getContext()
mAccount.getSavedAccount(), getContext()
)
);
}
@ -194,7 +238,7 @@ public class OwnCloudClient extends HttpClient {
return mCredentials instanceof OwnCloudAnonymousCredentials
? Uri.parse(mBaseUri + WEBDAV_UPLOADS_PATH_4_0)
: Uri.parse(mBaseUri + WEBDAV_UPLOADS_PATH_4_0 + AccountUtils.getUserId(
mAccount.getSavedAccount(), getContext()
mAccount.getSavedAccount(), getContext()
)
);
}
@ -229,16 +273,32 @@ public class OwnCloudClient extends HttpClient {
}
}
public void setCookiesForBaseUri(List<Cookie> cookies) {
public String getCookiesString() {
StringBuilder cookiesString = new StringBuilder();
List<Cookie> cookieList = getCookiesFromUrl(HttpUrl.parse(mBaseUri.toString()));
if (cookieList != null) {
for (Cookie cookie : cookieList) {
cookiesString.append(cookie.toString()).append(";");
}
}
return cookiesString.toString();
}
public void setCookiesForCurrentAccount(List<Cookie> cookies) {
getOkHttpClient().cookieJar().saveFromResponse(
HttpUrl.parse(mBaseUri.toString()),
HttpUrl.parse(getAccount().getBaseUri().toString()),
cookies
);
}
public List<Cookie> getCookiesForBaseUri() {
return getOkHttpClient().cookieJar().loadForRequest(
HttpUrl.parse(mBaseUri.toString()));
public OwnCloudVersion getOwnCloudVersion() {
return mVersion;
}
public void setOwnCloudVersion(OwnCloudVersion version) {
mVersion = version;
}
public OwnCloudAccount getAccount() {
@ -249,6 +309,94 @@ public class OwnCloudClient extends HttpClient {
this.mAccount = account;
}
/**
* Checks the status code of an execution and decides if should be repeated with fresh credentials.
* <p>
* Invalidates current credentials if the request failed as anauthorized.
* <p>
* Refresh current credentials if possible, and marks a retry.
*
* @param status
* @param repeatCounter
* @return
*/
private boolean checkUnauthorizedAccess(int status, int repeatCounter) {
boolean credentialsWereRefreshed = false;
if (shouldInvalidateAccountCredentials(status)) {
boolean invalidated = invalidateAccountCredentials();
if (invalidated) {
if (getCredentials().authTokenCanBeRefreshed() &&
repeatCounter < MAX_REPEAT_COUNT_WITH_FRESH_CREDENTIALS) {
try {
mAccount.loadCredentials(getContext());
// if mAccount.getCredentials().length() == 0 --> refresh failed
setCredentials(mAccount.getCredentials());
credentialsWereRefreshed = true;
} catch (AccountsException | IOException e) {
Timber.e(e, "Error while trying to refresh auth token for %s",
mAccount.getSavedAccount().name
);
}
}
if (!credentialsWereRefreshed && mSingleSessionManager != null) {
// if credentials are not refreshed, client must be removed
// from the OwnCloudClientManager to prevent it is reused once and again
mSingleSessionManager.removeClientFor(mAccount);
}
}
// else: onExecute will finish with status 401
}
return credentialsWereRefreshed;
}
/**
* Determines if credentials should be invalidated according the to the HTTPS status
* of a network request just performed.
*
* @param httpStatusCode Result of the last request ran with the 'credentials' belows.
* @return 'True' if credentials should and might be invalidated, 'false' if shouldn't or
* cannot be invalidated with the given arguments.
*/
private boolean shouldInvalidateAccountCredentials(int httpStatusCode) {
boolean shouldInvalidateAccountCredentials =
(httpStatusCode == HttpConstants.HTTP_UNAUTHORIZED);
shouldInvalidateAccountCredentials &= (mCredentials != null && // real credentials
!(mCredentials instanceof OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials));
// test if have all the needed to effectively invalidate ...
shouldInvalidateAccountCredentials &= (mAccount != null && mAccount.getSavedAccount() != null && getContext() != null);
return shouldInvalidateAccountCredentials;
}
/**
* Invalidates credentials stored for the given account in the system {@link AccountManager} and in
* current {@link SingleSessionManager#getDefaultSingleton()} instance.
* <p>
* {@link #shouldInvalidateAccountCredentials(int)} should be called first.
*
* @return 'True' if invalidation was successful, 'false' otherwise.
*/
private boolean invalidateAccountCredentials() {
AccountManager am = AccountManager.get(getContext());
am.invalidateAuthToken(
mAccount.getSavedAccount().type,
mCredentials.getAuthToken()
);
am.clearPassword(mAccount.getSavedAccount()); // being strict, only needed for Basic Auth credentials
return true;
}
public boolean followRedirects() {
return mFollowRedirects;
}
public void setFollowRedirects(boolean followRedirects) {
this.mFollowRedirects = followRedirects;
}

View File

@ -19,12 +19,40 @@
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.http
package com.owncloud.android.lib.common;
import com.facebook.stetho.okhttp3.StethoInterceptor
import android.content.Context;
import android.net.Uri;
object DebugInterceptorFactory {
fun getInterceptor() = StethoInterceptor()
import com.owncloud.android.lib.common.http.HttpClient;
import com.owncloud.android.lib.resources.status.GetRemoteStatusOperation;
public class OwnCloudClientFactory {
/**
* Creates a OwnCloudClient to access a URL and sets the desired parameters for ownCloud
* client connections.
*
* @param uri URL to the ownCloud server; BASE ENTRY POINT, not WebDavPATH
* @param context Android context where the OwnCloudClient is being created.
* @return A OwnCloudClient object ready to be used
*/
public static OwnCloudClient createOwnCloudClient(Uri uri, Context context, boolean followRedirects) {
OwnCloudClient client = new OwnCloudClient(uri);
client.setFollowRedirects(followRedirects);
HttpClient.setContext(context);
retrieveCookiesFromMiddleware(client);
return client;
}
private static void retrieveCookiesFromMiddleware(OwnCloudClient client) {
final GetRemoteStatusOperation statusOperation = new GetRemoteStatusOperation();
statusOperation.run(client);
}
}

View File

@ -31,6 +31,7 @@ import android.net.Uri;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.authentication.OwnCloudCredentials;
import com.owncloud.android.lib.common.http.HttpClient;
import timber.log.Timber;
import java.io.IOException;
@ -48,7 +49,6 @@ public class SingleSessionManager {
private static SingleSessionManager sDefaultSingleton;
private static String sUserAgent;
private static ConnectionValidator sConnectionValidator;
private ConcurrentMap<String, OwnCloudClient> mClientsWithKnownUsername = new ConcurrentHashMap<>();
private ConcurrentMap<String, OwnCloudClient> mClientsWithUnknownUsername = new ConcurrentHashMap<>();
@ -60,14 +60,6 @@ public class SingleSessionManager {
return sDefaultSingleton;
}
public static void setConnectionValidator(ConnectionValidator connectionValidator) {
sConnectionValidator = connectionValidator;
}
public static ConnectionValidator getConnectionValidator() {
return sConnectionValidator;
}
public static String getUserAgent() {
return sUserAgent;
}
@ -76,23 +68,7 @@ public class SingleSessionManager {
sUserAgent = userAgent;
}
private static OwnCloudClient createOwnCloudClient(Uri uri,
Context context,
ConnectionValidator connectionValidator,
SingleSessionManager singleSessionManager) {
OwnCloudClient client = new OwnCloudClient(uri, connectionValidator, true, singleSessionManager, context);
return client;
}
public OwnCloudClient getClientFor(OwnCloudAccount account,
Context context) throws OperationCanceledException,
AuthenticatorException, IOException {
return getClientFor(account, context, getConnectionValidator());
}
public OwnCloudClient getClientFor(OwnCloudAccount account,
Context context,
ConnectionValidator connectionValidator) throws OperationCanceledException,
public OwnCloudClient getClientFor(OwnCloudAccount account, Context context) throws OperationCanceledException,
AuthenticatorException, IOException {
Timber.d("getClientFor starting ");
@ -123,34 +99,15 @@ public class SingleSessionManager {
}
} else {
Timber.v("reusing client for account %s", accountName);
if (client.getAccount() != null &&
client.getAccount().getCredentials() != null &&
(client.getAccount().getCredentials().getAuthToken() == null || client.getAccount().getCredentials().getAuthToken().isEmpty())
) {
Timber.i("Client " + client.getAccount().getName() + " needs to refresh credentials");
//the next two lines are a hack because okHttpclient is used as a singleton instead of being an
//injected instance that can be deleted when required
client.clearCookies();
client.clearCredentials();
client.setAccount(account);
account.loadCredentials(context);
client.setCredentials(account.getCredentials());
Timber.i("Client " + account.getName() + " with credentials size" + client.getAccount().getCredentials().getAuthToken().length());
}
reusingKnown = true;
}
if (client == null) {
// no client to reuse - create a new one
client = createOwnCloudClient(
client = OwnCloudClientFactory.createOwnCloudClient(
account.getBaseUri(),
context,
connectionValidator,
this); // TODO remove dependency on OwnCloudClientFactory
context.getApplicationContext(),
true); // TODO remove dependency on OwnCloudClientFactory
//the next two lines are a hack because okHttpclient is used as a singleton instead of being an
//injected instance that can be deleted when required
@ -158,6 +115,7 @@ public class SingleSessionManager {
client.clearCredentials();
client.setAccount(account);
HttpClient.setContext(context);
account.loadCredentials(context);
client.setCredentials(account.getCredentials());

View File

@ -94,6 +94,26 @@ public class AccountUtils {
return username;
}
/**
* Get the stored server version corresponding to an OC account.
*
* @param account An OC account
* @param context Application context
* @return Version of the OC server, according to last check
*/
public static OwnCloudVersion getServerVersionForAccount(Account account, Context context) {
AccountManager ama = AccountManager.get(context);
OwnCloudVersion version = null;
try {
String versionString = ama.getUserData(account, Constants.KEY_OC_VERSION);
version = new OwnCloudVersion(versionString);
} catch (Exception e) {
Timber.e(e, "Couldn't get a the server version for an account");
}
return version;
}
/**
* @return
* @throws IOException
@ -112,7 +132,6 @@ public class AccountUtils {
String username = AccountUtils.getUsernameForAccount(account);
if (isOauth2) {
Timber.i("Trying to retrieve credentials for oAuth account" + account.name);
String accessToken = am.blockingGetAuthToken(
account,
AccountTypeUtils.getAuthTokenTypeAccessToken(account.type),
@ -190,6 +209,11 @@ public class AccountUtils {
}
public static class Constants {
/**
* Version should be 3 numbers separated by dot so it can be parsed by
* {@link OwnCloudVersion}
*/
public static final String KEY_OC_VERSION = "oc_version";
/**
* Base url should point to owncloud installation without trailing / ie:
* http://server/path or https://owncloud.server

View File

@ -28,7 +28,7 @@ import okhttp3.CookieJar
import okhttp3.HttpUrl
class CookieJarImpl(
private val cookieStore: HashMap<String, List<Cookie>>
private val sCookieStore: HashMap<String, List<Cookie>>
) : CookieJar {
fun containsCookieWithName(cookies: List<Cookie>, name: String): Boolean {
@ -52,11 +52,12 @@ class CookieJarImpl(
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
// Avoid duplicated cookies but update
val currentCookies: List<Cookie> = cookieStore[url.host] ?: ArrayList()
val currentCookies: List<Cookie> = sCookieStore[url.host] ?: ArrayList()
val updatedCookies: List<Cookie> = getUpdatedCookies(currentCookies, cookies)
cookieStore[url.host] = updatedCookies
sCookieStore[url.host] = updatedCookies
}
override fun loadForRequest(url: HttpUrl) =
cookieStore[url.host] ?: ArrayList()
}
sCookieStore[url.host] ?: ArrayList()
}

View File

@ -40,6 +40,7 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.HashMap;
@ -53,46 +54,32 @@ import java.util.concurrent.TimeUnit;
*/
public class HttpClient {
private Context mContext;
private HashMap<String, List<Cookie>> mCookieStore = new HashMap<>();
private LogInterceptor mLogInterceptor = new LogInterceptor();
private static OkHttpClient sOkHttpClient;
private static Context sContext;
private static HashMap<String, List<Cookie>> sCookieStore = new HashMap<>();
private static LogInterceptor sLogInterceptor;
private OkHttpClient mOkHttpClient = null;
protected HttpClient(Context context) {
if (context == null) {
Timber.e("Context may not be NULL!");
throw new NullPointerException("Context may not be NULL!");
}
mContext = context;
}
public OkHttpClient getOkHttpClient() {
if (mOkHttpClient == null) {
public static OkHttpClient getOkHttpClient() {
if (sOkHttpClient == null) {
try {
final X509TrustManager trustManager = new AdvancedX509TrustManager(
NetworkUtils.getKnownServersStore(mContext));
final SSLContext sslContext = buildSSLContext();
sslContext.init(null, new TrustManager[]{trustManager}, null);
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
NetworkUtils.getKnownServersStore(sContext));
final SSLSocketFactory sslSocketFactory = getNewSslSocketFactory(trustManager);
// Automatic cookie handling, NOT PERSISTENT
final CookieJar cookieJar = new CookieJarImpl(mCookieStore);
mOkHttpClient = buildNewOkHttpClient(sslSocketFactory, trustManager, cookieJar);
final CookieJar cookieJar = new CookieJarImpl(sCookieStore);
// TODO: Not verifying the hostname against certificate. ask owncloud security human if this is ok.
//.hostnameVerifier(new BrowserCompatHostnameVerifier());
sOkHttpClient = buildNewOkHttpClient(sslSocketFactory, trustManager, cookieJar);
} catch (NoSuchAlgorithmException nsae) {
Timber.e(nsae, "Could not setup SSL system.");
throw new RuntimeException("Could not setup okHttp client.", nsae);
} catch (Exception e) {
Timber.e(e, "Could not setup okHttp client.");
throw new RuntimeException("Could not setup okHttp client.", e);
Timber.e(e, "Could not setup SSL system.");
}
}
return mOkHttpClient;
return sOkHttpClient;
}
private SSLContext buildSSLContext() throws NoSuchAlgorithmException {
private static SSLContext getSslContext() throws NoSuchAlgorithmException {
try {
return SSLContext.getInstance(TlsVersion.TLS_1_3.javaName());
} catch (NoSuchAlgorithmException tlsv13Exception) {
@ -113,11 +100,17 @@ public class HttpClient {
}
}
private OkHttpClient buildNewOkHttpClient(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager,
CookieJar cookieJar) {
private static SSLSocketFactory getNewSslSocketFactory(X509TrustManager trustManager)
throws NoSuchAlgorithmException, KeyManagementException {
final SSLContext sslContext = getSslContext();
sslContext.init(null, new TrustManager[]{trustManager}, null);
return sslContext.getSocketFactory();
}
private static OkHttpClient buildNewOkHttpClient(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager,
CookieJar cookieJar) {
return new OkHttpClient.Builder()
.addNetworkInterceptor(getLogInterceptor())
.addNetworkInterceptor(DebugInterceptorFactory.INSTANCE.getInterceptor())
.protocols(Collections.singletonList(Protocol.HTTP_1_1))
.readTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS)
@ -129,19 +122,26 @@ public class HttpClient {
.build();
}
public static LogInterceptor getLogInterceptor() {
if (sLogInterceptor == null) {
sLogInterceptor = new LogInterceptor();
}
return sLogInterceptor;
}
public static List<Cookie> getCookiesFromUrl(HttpUrl httpUrl) {
return sCookieStore.get(httpUrl.host());
}
public Context getContext() {
return mContext;
return sContext;
}
public LogInterceptor getLogInterceptor() {
return mLogInterceptor;
}
public List<Cookie> getCookiesFromUrl(HttpUrl httpUrl) {
return mCookieStore.get(httpUrl.host());
public static void setContext(Context context) {
sContext = context;
}
public void clearCookies() {
mCookieStore.clear();
sCookieStore.clear();
}
}

View File

@ -40,7 +40,6 @@ public class HttpConstants {
public static final String IF_MATCH_HEADER = "If-Match";
public static final String IF_NONE_MATCH_HEADER = "If-None-Match";
public static final String CONTENT_TYPE_HEADER = "Content-Type";
public static final String ACCEPT_LANGUAGE_HEADER = "Accept-Language";
public static final String CONTENT_LENGTH_HEADER = "Content-Length";
public static final String OC_TOTAL_LENGTH_HEADER = "OC-Total-Length";
public static final String OC_X_OC_MTIME_HEADER = "X-OC-Mtime";
@ -185,7 +184,6 @@ public class HttpConstants {
public static final int HTTP_LOCKED = 423;
// 424 Failed Dependency (WebDAV - RFC 2518)
public static final int HTTP_FAILED_DEPENDENCY = 424;
public static final int HTTP_TOO_EARLY = 425;
/**
* 5xx Client Error

View File

@ -51,7 +51,7 @@ class LogInterceptor : Interceptor {
val request = chain.request().also {
val requestId = it.headers[OC_X_REQUEST_ID]
logHttp(REQUEST, INFO, requestId, "Method: ${it.method} URL: ${it.url}")
logHttp(REQUEST, INFO, requestId, "Type: ${it.method} URL: ${it.url}")
logHeaders(requestId, it.headers, REQUEST)
logRequestBody(requestId, it.body)
}
@ -64,7 +64,7 @@ class LogInterceptor : Interceptor {
RESPONSE,
INFO,
requestId,
"Method: ${request.method} URL: ${request.url} Code: ${it.code} Message: ${it.message}"
"Code: ${it.code} Message: ${it.message} IsSuccessful: ${it.isSuccessful}"
)
logHeaders(requestId, it.headers, RESPONSE)
logResponseBody(requestId, it.body)

View File

@ -1,27 +1,3 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.http.methods
import com.owncloud.android.lib.common.http.HttpClient
@ -38,41 +14,23 @@ import java.net.URL
import java.util.concurrent.TimeUnit
abstract class HttpBaseMethod constructor(url: URL) {
var okHttpClient: OkHttpClient
var httpUrl: HttpUrl = url.toHttpUrlOrNull() ?: throw MalformedURLException()
var request: Request
var followPermanentRedirects = false
abstract var response: Response
var call: Call? = null
var followRedirects: Boolean = true
var retryOnConnectionFailure: Boolean = true
var connectionTimeoutVal: Long? = null
var connectionTimeoutUnit: TimeUnit? = null
var readTimeoutVal: Long? = null
private set
var readTimeoutUnit: TimeUnit? = null
private set
init {
okHttpClient = HttpClient.getOkHttpClient()
request = Request.Builder()
.url(httpUrl)
.build()
}
@Throws(Exception::class)
open fun execute(httpClient: HttpClient): Int {
val okHttpClient = httpClient.okHttpClient.newBuilder().apply {
retryOnConnectionFailure(retryOnConnectionFailure)
followRedirects(followRedirects)
readTimeoutUnit?.let { unit ->
readTimeoutVal?.let { readTimeout(it, unit) }
}
connectionTimeoutUnit?.let { unit ->
connectionTimeoutVal?.let { connectTimeout(it, unit) }
}
}.build()
return onExecute(okHttpClient)
open fun execute(): Int {
return onExecute()
}
open fun setUrl(url: HttpUrl) {
@ -141,11 +99,6 @@ abstract class HttpBaseMethod constructor(url: URL) {
return response.body?.byteStream()
}
/**
* returns the final url after following the last redirect.
*/
open fun getFinalUrl() = response.request.url
/*************************
*** Connection Params ***
*************************/
@ -154,19 +107,31 @@ abstract class HttpBaseMethod constructor(url: URL) {
// Setter
//////////////////////////////
// Connection parameters
open fun setRetryOnConnectionFailure(retryOnConnectionFailure: Boolean) {
okHttpClient = okHttpClient.newBuilder()
.retryOnConnectionFailure(retryOnConnectionFailure)
.build()
}
open fun setReadTimeout(readTimeout: Long, timeUnit: TimeUnit) {
readTimeoutVal = readTimeout
readTimeoutUnit = timeUnit
okHttpClient = okHttpClient.newBuilder()
.readTimeout(readTimeout, timeUnit)
.build()
}
open fun setConnectionTimeout(
connectionTimeout: Long,
timeUnit: TimeUnit
) {
connectionTimeoutVal = connectionTimeout
connectionTimeoutUnit = timeUnit
okHttpClient = okHttpClient.newBuilder()
.readTimeout(connectionTimeout, timeUnit)
.build()
}
open fun setFollowRedirects(followRedirects: Boolean) {
okHttpClient = okHttpClient.newBuilder()
.followRedirects(followRedirects)
.build()
}
/************
@ -183,5 +148,5 @@ abstract class HttpBaseMethod constructor(url: URL) {
// For override
//////////////////////////////
@Throws(Exception::class)
protected abstract fun onExecute(okHttpClient: OkHttpClient): Int
protected abstract fun onExecute(): Int
}

View File

@ -23,7 +23,6 @@
*/
package com.owncloud.android.lib.common.http.methods.nonwebdav
import okhttp3.OkHttpClient
import java.io.IOException
import java.net.URL
@ -34,10 +33,10 @@ import java.net.URL
*/
class DeleteMethod(url: URL) : HttpMethod(url) {
@Throws(IOException::class)
override fun onExecute(okHttpClient: OkHttpClient): Int {
override fun onExecute(): Int {
request = request.newBuilder()
.delete()
.build()
return super.onExecute(okHttpClient)
return super.onExecute()
}
}

View File

@ -23,7 +23,6 @@
*/
package com.owncloud.android.lib.common.http.methods.nonwebdav
import okhttp3.OkHttpClient
import java.io.IOException
import java.net.URL
@ -34,10 +33,10 @@ import java.net.URL
*/
class GetMethod(url: URL) : HttpMethod(url) {
@Throws(IOException::class)
override fun onExecute(okHttpClient: OkHttpClient): Int {
override fun onExecute(): Int {
request = request.newBuilder()
.get()
.build()
return super.onExecute(okHttpClient)
return super.onExecute()
}
}

View File

@ -24,7 +24,6 @@
package com.owncloud.android.lib.common.http.methods.nonwebdav
import com.owncloud.android.lib.common.http.methods.HttpBaseMethod
import okhttp3.OkHttpClient
import okhttp3.Response
import java.net.URL
@ -39,7 +38,7 @@ abstract class HttpMethod(
override lateinit var response: Response
public override fun onExecute(okHttpClient: OkHttpClient): Int {
public override fun onExecute(): Int {
call = okHttpClient.newCall(request)
call?.let { response = it.execute() }
return super.statusCode

View File

@ -23,7 +23,6 @@
*/
package com.owncloud.android.lib.common.http.methods.nonwebdav
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import java.io.IOException
import java.net.URL
@ -38,10 +37,10 @@ class PostMethod(
private val postRequestBody: RequestBody
) : HttpMethod(url) {
@Throws(IOException::class)
override fun onExecute(okHttpClient: OkHttpClient): Int {
override fun onExecute(): Int {
request = request.newBuilder()
.post(postRequestBody)
.build()
return super.onExecute(okHttpClient)
return super.onExecute()
}
}

View File

@ -23,7 +23,6 @@
*/
package com.owncloud.android.lib.common.http.methods.nonwebdav
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import java.io.IOException
import java.net.URL
@ -38,10 +37,10 @@ class PutMethod(
private val putRequestBody: RequestBody
) : HttpMethod(url) {
@Throws(IOException::class)
override fun onExecute(okHttpClient: OkHttpClient): Int {
override fun onExecute(): Int {
request = request.newBuilder()
.put(putRequestBody)
.build()
return super.onExecute(okHttpClient)
return super.onExecute()
}
}

View File

@ -23,7 +23,6 @@
*/
package com.owncloud.android.lib.common.http.methods.webdav
import at.bitfire.dav4jvm.DavOCResource
import okhttp3.Response
import java.net.URL
@ -36,10 +35,10 @@ import java.net.URL
class CopyMethod(
val url: URL,
private val destinationUrl: String,
val forceOverride: Boolean = false
private val forceOverride: Boolean
) : DavMethod(url) {
@Throws(Exception::class)
public override fun onDavExecute(davResource: DavOCResource): Int {
public override fun onExecute(): Int {
davResource.copy(
destinationUrl,
forceOverride,

View File

@ -29,11 +29,14 @@ import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.dav4jvm.exception.RedirectException
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.HttpBaseMethod
import okhttp3.OkHttpClient
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Protocol
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import java.net.MalformedURLException
import java.net.URL
import java.util.concurrent.TimeUnit
/**
* Wrapper to perform WebDAV (dav4android) calls
@ -41,25 +44,27 @@ import java.net.URL
* @author David González Verdugo
*/
abstract class DavMethod protected constructor(url: URL) : HttpBaseMethod(url) {
override lateinit var response: Response
private var davResource: DavOCResource? = null
protected var davResource: DavOCResource
override fun abort() {
davResource?.cancelCall()
override lateinit var response: Response
init {
val httpUrl = url.toHttpUrlOrNull() ?: throw MalformedURLException()
davResource = DavOCResource(
okHttpClient,
httpUrl,
log
)
}
protected abstract fun onDavExecute(davResource: DavOCResource): Int
override fun abort() {
davResource.cancelCall()
}
@Throws(Exception::class)
override fun onExecute(okHttpClient: OkHttpClient): Int {
override fun execute(): Int {
return try {
davResource = DavOCResource(
okHttpClient.newBuilder().followRedirects(false).build(),
httpUrl,
log
)
onDavExecute(davResource!!)
onExecute()
} catch (httpException: HttpException) {
// Modify responses with information gathered from exceptions
if (httpException is RedirectException) {
@ -86,12 +91,71 @@ abstract class DavMethod protected constructor(url: URL) : HttpBaseMethod(url) {
}
}
//////////////////////////////
// Setter
//////////////////////////////
// Connection parameters
override fun setReadTimeout(readTimeout: Long, timeUnit: TimeUnit) {
super.setReadTimeout(readTimeout, timeUnit)
davResource = DavOCResource(
okHttpClient,
request.url,
log
)
}
override fun setConnectionTimeout(
connectionTimeout: Long,
timeUnit: TimeUnit
) {
super.setConnectionTimeout(connectionTimeout, timeUnit)
davResource = DavOCResource(
okHttpClient,
request.url,
log
)
}
override fun setFollowRedirects(followRedirects: Boolean) {
super.setFollowRedirects(followRedirects)
davResource = DavOCResource(
okHttpClient,
request.url,
log
)
}
override fun setUrl(url: HttpUrl) {
super.setUrl(url)
davResource = DavOCResource(
okHttpClient,
request.url,
log
)
}
override fun setRequestHeader(name: String, value: String) {
super.setRequestHeader(name, value)
davResource = DavOCResource(
okHttpClient,
request.url,
log
)
}
//////////////////////////////
// Getter
//////////////////////////////
override fun setRetryOnConnectionFailure(retryOnConnectionFailure: Boolean) {
super.setRetryOnConnectionFailure(retryOnConnectionFailure)
davResource = DavOCResource(
okHttpClient,
request.url,
log
)
}
override val isAborted: Boolean
get() = davResource?.isCallAborted() ?: false
get() = davResource.isCallAborted()
}

View File

@ -24,36 +24,12 @@
package com.owncloud.android.lib.common.http.methods.webdav
import at.bitfire.dav4jvm.Property
import at.bitfire.dav4jvm.PropertyUtils.getAllPropSet
import at.bitfire.dav4jvm.PropertyUtils.getQuotaPropset
import at.bitfire.dav4jvm.property.CreationDate
import at.bitfire.dav4jvm.property.DisplayName
import at.bitfire.dav4jvm.property.GetContentLength
import at.bitfire.dav4jvm.property.GetContentType
import at.bitfire.dav4jvm.property.GetETag
import at.bitfire.dav4jvm.property.GetLastModified
import at.bitfire.dav4jvm.property.OCId
import at.bitfire.dav4jvm.property.OCPermissions
import at.bitfire.dav4jvm.property.OCPrivatelink
import at.bitfire.dav4jvm.property.OCSize
import at.bitfire.dav4jvm.property.ResourceType
import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes
object DavUtils {
@JvmStatic val allPropSet: Array<Property.Name>
get() = arrayOf(
DisplayName.NAME,
GetContentType.NAME,
ResourceType.NAME,
GetContentLength.NAME,
GetLastModified.NAME,
CreationDate.NAME,
GetETag.NAME,
OCPermissions.NAME,
OCId.NAME,
OCSize.NAME,
OCPrivatelink.NAME,
OCShareTypes.NAME,
)
@JvmStatic val allPropset: Array<Property.Name>
get() = getAllPropSet()
val quotaPropSet: Array<Property.Name>
get() = getQuotaPropset()

View File

@ -23,7 +23,6 @@
*/
package com.owncloud.android.lib.common.http.methods.webdav
import at.bitfire.dav4jvm.DavOCResource
import okhttp3.Response
import java.net.URL
@ -35,7 +34,7 @@ import java.net.URL
*/
class MkColMethod(url: URL) : DavMethod(url) {
@Throws(Exception::class)
public override fun onDavExecute(davResource: DavOCResource): Int {
public override fun onExecute(): Int {
davResource.mkCol(
xmlBody = null,
listOfHeaders = super.getRequestHeadersAsHashMap()

View File

@ -23,7 +23,6 @@
*/
package com.owncloud.android.lib.common.http.methods.webdav
import at.bitfire.dav4jvm.DavOCResource
import okhttp3.Response
import java.net.URL
@ -36,10 +35,10 @@ import java.net.URL
class MoveMethod(
url: URL,
private val destinationUrl: String,
val forceOverride: Boolean = false
private val forceOverride: Boolean
) : DavMethod(url) {
@Throws(Exception::class)
override fun onDavExecute(davResource: DavOCResource): Int {
public override fun onExecute(): Int {
davResource.move(
destinationUrl,
forceOverride,

View File

@ -23,7 +23,6 @@
*/
package com.owncloud.android.lib.common.http.methods.webdav
import at.bitfire.dav4jvm.DavOCResource
import at.bitfire.dav4jvm.Property
import at.bitfire.dav4jvm.Response
import at.bitfire.dav4jvm.Response.HrefRelation
@ -48,12 +47,12 @@ class PropfindMethod(
private set
@Throws(IOException::class, DavException::class)
public override fun onDavExecute(davResource: DavOCResource): Int {
public override fun onExecute(): Int {
davResource.propfind(
depth = depth,
reqProp = propertiesToRequest,
reqProp = *propertiesToRequest,
listOfHeaders = super.getRequestHeadersAsHashMap(),
callback = { response: Response, hrefRelation: HrefRelation ->
callback = { response: Response, hrefRelation: HrefRelation? ->
when (hrefRelation) {
HrefRelation.MEMBER -> members.add(response)
HrefRelation.SELF -> this.root = response

View File

@ -23,7 +23,6 @@
*/
package com.owncloud.android.lib.common.http.methods.webdav
import at.bitfire.dav4jvm.DavOCResource
import at.bitfire.dav4jvm.exception.HttpException
import com.owncloud.android.lib.common.http.HttpConstants
import okhttp3.RequestBody
@ -40,7 +39,7 @@ class PutMethod(
private val putRequestBody: RequestBody
) : DavMethod(url) {
@Throws(IOException::class, HttpException::class)
public override fun onDavExecute(davResource: DavOCResource): Int {
public override fun onExecute(): Int {
davResource.put(
putRequestBody,
super.getRequestHeader(HttpConstants.IF_MATCH_HEADER),

View File

@ -1,44 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.http.methods.webdav.properties
import at.bitfire.dav4jvm.Property
import at.bitfire.dav4jvm.XmlUtils
import org.xmlpull.v1.XmlPullParser
class OCShareTypes : ShareTypeListProperty() {
class Factory : ShareTypeListProperty.Factory() {
override fun create(parser: XmlPullParser) =
create(parser, OCShareTypes())
override fun getName(): Property.Name = NAME
}
companion object {
@JvmField
val NAME = Property.Name(XmlUtils.NS_OWNCLOUD, "share-types")
}
}

View File

@ -1,46 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.http.methods.webdav.properties
import at.bitfire.dav4jvm.Property
import at.bitfire.dav4jvm.PropertyFactory
import at.bitfire.dav4jvm.XmlUtils
import org.xmlpull.v1.XmlPullParser
import java.util.LinkedList
abstract class ShareTypeListProperty : Property {
val shareTypes = LinkedList<String>()
override fun toString() = "share types =[" + shareTypes.joinToString(", ") + "]"
abstract class Factory : PropertyFactory {
fun create(parser: XmlPullParser, list: ShareTypeListProperty): ShareTypeListProperty {
XmlUtils.readTextPropertyList(parser, Property.Name(XmlUtils.NS_OWNCLOUD, "share-type"), list.shareTypes)
return list
}
}
}

View File

@ -0,0 +1,114 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.network;
import okhttp3.MediaType;
import okio.BufferedSink;
import timber.log.Timber;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Iterator;
/**
* A Request body that represents a file chunk and include information about the progress when uploading it
*
* @author David González Verdugo
*/
public class ChunkFromFileRequestBody extends FileRequestBody {
private final FileChannel mChannel;
private final long mChunkSize;
private long mOffset;
private long mTransferred;
private ByteBuffer mBuffer = ByteBuffer.allocate(4096);
public ChunkFromFileRequestBody(File file, MediaType contentType, FileChannel channel, long chunkSize) {
super(file, contentType);
if (channel == null) {
throw new IllegalArgumentException("File may not be null");
}
if (chunkSize <= 0) {
throw new IllegalArgumentException("Chunk size must be greater than zero");
}
this.mChannel = channel;
this.mChunkSize = chunkSize;
mOffset = 0;
mTransferred = 0;
}
@Override
public long contentLength() {
try {
return Math.min(mChunkSize, mChannel.size() - mChannel.position());
} catch (IOException e) {
return mChunkSize;
}
}
@Override
public void writeTo(BufferedSink sink) {
int readCount;
Iterator<OnDatatransferProgressListener> it;
try {
mChannel.position(mOffset);
long size = mFile.length();
if (size == 0) {
size = -1;
}
long maxCount = Math.min(mOffset + mChunkSize, mChannel.size());
while (mChannel.position() < maxCount) {
readCount = mChannel.read(mBuffer);
int bytesToWriteInBuffer = (int) Math.min(readCount, mFile.length() - mTransferred);
sink.getBuffer().write(mBuffer.array(), 0, bytesToWriteInBuffer);
sink.flush();
mBuffer.clear();
if (mTransferred < maxCount) { // condition to avoid accumulate progress for repeated chunks
mTransferred += readCount;
}
synchronized (mDataTransferListeners) {
it = mDataTransferListeners.iterator();
while (it.hasNext()) {
it.next().onTransferProgress(readCount, mTransferred, size, mFile.getAbsolutePath());
}
}
}
} catch (Exception exception) {
Timber.e(exception, "Transferred " + mTransferred + " bytes from a total of " + mFile.length());
}
}
public void setOffset(long offset) {
this.mOffset = offset;
}
}

View File

@ -1,92 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.network
import com.owncloud.android.lib.resources.files.chunks.ChunkedUploadFromFileSystemOperation.Companion.CHUNK_SIZE
import okhttp3.MediaType
import okio.BufferedSink
import timber.log.Timber
import java.io.File
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
/**
* A Request body that represents a file chunk and include information about the progress when uploading it
*
* @author David González Verdugo
*/
class ChunkFromFileRequestBody(
file: File,
contentType: MediaType?,
private val channel: FileChannel,
private val chunkSize: Long = CHUNK_SIZE
) : FileRequestBody(file, contentType) {
private var offset: Long = 0
private var alreadyTransferred: Long = 0
private val buffer = ByteBuffer.allocate(4_096)
init {
require(chunkSize > 0) { "Chunk size must be greater than zero" }
}
override fun contentLength(): Long {
return chunkSize.coerceAtMost(channel.size() - channel.position())
}
override fun writeTo(sink: BufferedSink) {
var readCount: Int
var iterator: Iterator<OnDatatransferProgressListener>
try {
channel.position(offset)
val maxCount = (offset + chunkSize).coerceAtMost(channel.size())
while (channel.position() < maxCount) {
readCount = channel.read(buffer)
val bytesToWriteInBuffer = readCount.toLong().coerceAtMost(file.length() - alreadyTransferred).toInt()
sink.buffer.write(buffer.array(), 0, bytesToWriteInBuffer)
sink.flush()
buffer.clear()
if (alreadyTransferred < maxCount) { // condition to avoid accumulate progress for repeated chunks
alreadyTransferred += readCount.toLong()
}
synchronized(dataTransferListeners) {
iterator = dataTransferListeners.iterator()
while (iterator.hasNext()) {
iterator.next().onTransferProgress(readCount.toLong(), alreadyTransferred, file.length(), file.absolutePath)
}
}
}
} catch (exception: Exception) {
Timber.e(exception, "Transferred " + alreadyTransferred + " bytes from a total of " + file.length())
}
}
fun setOffset(newOffset: Long) {
offset = newOffset
}
}

View File

@ -1,117 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.network
import android.content.ContentResolver
import android.net.Uri
import android.provider.OpenableColumns
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okio.BufferedSink
import okio.Source
import okio.source
import timber.log.Timber
import java.io.IOException
class ContentUriRequestBody(
private val contentResolver: ContentResolver,
private val contentUri: Uri
) : RequestBody(), ProgressiveDataTransferer {
private val dataTransferListeners: MutableSet<OnDatatransferProgressListener> = HashSet()
val fileSize: Long = contentResolver.query(contentUri, null, null, null, null)?.use { cursor ->
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
cursor.moveToFirst()
cursor.getLong(sizeIndex)
} ?: -1
override fun contentType(): MediaType? {
val contentType = contentResolver.getType(contentUri) ?: return null
return contentType.toMediaTypeOrNull()
}
override fun contentLength(): Long {
return fileSize
}
override fun writeTo(sink: BufferedSink) {
val inputStream = contentResolver.openInputStream(contentUri)
?: throw IOException("Couldn't open content URI for reading: $contentUri")
val previousTime = System.currentTimeMillis()
sink.writeAndUpdateProgress(inputStream.source())
inputStream.source().close()
val laterTime = System.currentTimeMillis()
Timber.d("Difference - ${laterTime - previousTime} milliseconds")
}
private fun BufferedSink.writeAndUpdateProgress(source: Source) {
var iterator: Iterator<OnDatatransferProgressListener>
try {
var totalBytesRead = 0L
var read: Long
while (source.read(this.buffer, BYTES_TO_READ).also { read = it } != -1L) {
totalBytesRead += read
this.flush()
synchronized(dataTransferListeners) {
iterator = dataTransferListeners.iterator()
while (iterator.hasNext()) {
iterator.next().onTransferProgress(read, totalBytesRead, fileSize, contentUri.toString())
}
}
}
} catch (e: Exception) {
Timber.e(e)
}
}
override fun addDatatransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListeners) {
dataTransferListeners.add(listener)
}
}
override fun addDatatransferProgressListeners(listeners: MutableCollection<OnDatatransferProgressListener>) {
synchronized(dataTransferListeners) {
dataTransferListeners.addAll(listeners)
}
}
override fun removeDatatransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListeners) {
dataTransferListeners.remove(listener)
}
}
companion object {
private const val BYTES_TO_READ = 4_096L
}
}

View File

@ -0,0 +1,119 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.network;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.BufferedSink;
import okio.Okio;
import okio.Source;
import timber.log.Timber;
import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* A Request body that represents a file and include information about the progress when uploading it
*
* @author David González Verdugo
*/
public class FileRequestBody extends RequestBody implements ProgressiveDataTransferer {
final Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<>();
protected File mFile;
private MediaType mContentType;
public FileRequestBody(File file, MediaType contentType) {
mFile = file;
mContentType = contentType;
}
@Override
public boolean isOneShot() {
return true;
}
@Override
public MediaType contentType() {
return mContentType;
}
@Override
public long contentLength() {
return mFile.length();
}
@Override
public void writeTo(BufferedSink sink) {
Source source;
Iterator<OnDatatransferProgressListener> it;
try {
source = Okio.source(mFile);
long transferred = 0;
long read;
while ((read = source.read(sink.buffer(), 4096)) != -1) {
transferred += read;
sink.flush();
synchronized (mDataTransferListeners) {
it = mDataTransferListeners.iterator();
while (it.hasNext()) {
it.next().onTransferProgress(read, transferred, mFile.length(), mFile.getAbsolutePath());
}
}
}
Timber.d("File with name " + mFile.getName() + " and size " + mFile.length() + " written in request body");
} catch (Exception e) {
Timber.e(e);
}
}
@Override
public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.add(listener);
}
}
@Override
public void addDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.addAll(listeners);
}
}
@Override
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.remove(listener);
}
}
}

View File

@ -1,97 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.network
import okhttp3.MediaType
import okhttp3.RequestBody
import okio.BufferedSink
import okio.Source
import okio.source
import timber.log.Timber
import java.io.File
import java.util.HashSet
/**
* A Request body that represents a file and include information about the progress when uploading it
*
* @author David González Verdugo
*/
open class FileRequestBody(
val file: File,
private val contentType: MediaType?,
) : RequestBody(), ProgressiveDataTransferer {
val dataTransferListeners: MutableSet<OnDatatransferProgressListener> = HashSet()
override fun isOneShot(): Boolean = true
override fun contentType(): MediaType? = contentType
override fun contentLength(): Long = file.length()
override fun writeTo(sink: BufferedSink) {
val source: Source
var it: Iterator<OnDatatransferProgressListener>
try {
source = file.source()
var transferred: Long = 0
var read: Long
while (source.read(sink.buffer, BYTES_TO_READ).also { read = it } != -1L) {
transferred += read
sink.flush()
synchronized(dataTransferListeners) {
it = dataTransferListeners.iterator()
while (it.hasNext()) {
it.next().onTransferProgress(read, transferred, file.length(), file.absolutePath)
}
}
}
Timber.d("File with name ${file.name} and size ${file.length()} written in request body")
} catch (e: Exception) {
Timber.e(e)
}
}
override fun addDatatransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListeners) {
dataTransferListeners.add(listener)
}
}
override fun addDatatransferProgressListeners(listeners: Collection<OnDatatransferProgressListener>) {
synchronized(dataTransferListeners) {
dataTransferListeners.addAll(listeners)
}
}
override fun removeDatatransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListeners) {
dataTransferListeners.remove(listener)
}
}
companion object {
private const val BYTES_TO_READ = 4_096L
}
}

View File

@ -1,27 +1,3 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.operations;
import android.accounts.Account;
@ -159,7 +135,7 @@ public abstract class RemoteOperation<T> implements Runnable {
if (mAccount != null && mContext != null) {
OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext);
mClient = SingleSessionManager.getDefaultSingleton().
getClientFor(ocAccount, mContext, SingleSessionManager.getConnectionValidator());
getClientFor(ocAccount, mContext);
} else {
throw new IllegalStateException("Trying to run a remote operation " +
"asynchronously with no client and no chance to create one (no account)");
@ -289,4 +265,4 @@ public abstract class RemoteOperation<T> implements Runnable {
mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend);
}
}
}
}

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -34,7 +34,6 @@ import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.HttpBaseMethod;
import com.owncloud.android.lib.common.network.CertificateCombinedException;
import okhttp3.Headers;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.json.JSONException;
import timber.log.Timber;
@ -46,7 +45,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
@ -61,15 +59,13 @@ public class RemoteOperationResult<T>
* Generated - should be refreshed every time the class changes!!
*/
private static final long serialVersionUID = 4968939884332372230L;
private static final String LOCATION = "location";
private static final String WWW_AUTHENTICATE = "www-authenticate";
private boolean mSuccess = false;
private int mHttpCode = -1;
private String mHttpPhrase = null;
private Exception mException = null;
private ResultCode mCode = ResultCode.UNKNOWN_ERROR;
private String mRedirectedLocation = "";
private String mRedirectedLocation;
private List<String> mAuthenticate = new ArrayList<>();
private String mLastPermanentLocation = null;
private T mData = null;
@ -116,14 +112,6 @@ public class RemoteOperationResult<T>
*/
public RemoteOperationResult(Exception e) {
mException = e;
//TODO: Do propper exception handling and remove this
Timber.e("---------------------------------" +
"\nCreate RemoteOperationResult from exception." +
"\n Message: %s" +
"\n Stacktrace: %s" +
"\n---------------------------------",
ExceptionUtils.getMessage(e),
ExceptionUtils.getStackTrace(e));
if (e instanceof OperationCancelledException) {
mCode = ResultCode.CANCELLED;
@ -167,10 +155,7 @@ public class RemoteOperationResult<T>
} else if (e instanceof FileNotFoundException) {
mCode = ResultCode.LOCAL_FILE_NOT_FOUND;
} else if (e instanceof ProtocolException) {
mCode = ResultCode.NETWORK_ERROR;
}
else {
} else {
mCode = ResultCode.UNKNOWN_ERROR;
}
}
@ -241,10 +226,6 @@ public class RemoteOperationResult<T>
httpMethod.getResponseBodyAsString(),
ResultCode.SPECIFIC_METHOD_NOT_ALLOWED
);
break;
case HttpConstants.HTTP_TOO_EARLY:
mCode = ResultCode.TOO_EARLY;
break;
default:
break;
}
@ -267,11 +248,11 @@ public class RemoteOperationResult<T>
this(httpCode, httpPhrase);
if (headers != null) {
for (Map.Entry<String, List<String>> header : headers.toMultimap().entrySet()) {
if (LOCATION.equalsIgnoreCase(header.getKey())) {
if ("location".equals(header.getKey().toLowerCase())) {
mRedirectedLocation = header.getValue().get(0);
continue;
}
if (WWW_AUTHENTICATE.equalsIgnoreCase(header.getKey())) {
if ("www-authenticate".equals(header.getKey().toLowerCase())) {
for (String value: header.getValue()) {
mAuthenticate.add(value.toLowerCase());
}
@ -340,7 +321,7 @@ public class RemoteOperationResult<T>
mHttpPhrase = errorMessage;
}
} catch (Exception e) {
Timber.w("Error reading exception from server: %s\nTrace: %s", e.getMessage(), ExceptionUtils.getStackTrace(e));
Timber.w("Error reading exception from server: %s", e.getMessage());
// mCode stays as set in this(success, httpCode, headers)
}
}
@ -591,8 +572,6 @@ public class RemoteOperationResult<T>
SPECIFIC_SERVICE_UNAVAILABLE,
SPECIFIC_UNSUPPORTED_MEDIA_TYPE,
SPECIFIC_METHOD_NOT_ALLOWED,
SPECIFIC_BAD_REQUEST,
TOO_EARLY,
NETWORK_ERROR,
SPECIFIC_BAD_REQUEST
}
}

View File

@ -1,27 +1,3 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.utils
import info.hannes.timber.FileLoggingTree

View File

@ -36,7 +36,7 @@ data class CommonOcsResponse<T>(
@JsonClass(generateAdapter = true)
data class OCSResponse<T>(
val meta: MetaData,
val data: T?
val data: T
)
@JsonClass(generateAdapter = true)

View File

@ -1,27 +1,21 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
/**
* ownCloud Android client application
*
* @author David González Verdugo
* @author David González Verdugo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* Copyright (C) 2020 ownCloud GmbH.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.lib.resources

View File

@ -1,108 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.appregistry
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.nonwebdav.PostMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.squareup.moshi.Json
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import okhttp3.FormBody
import okhttp3.RequestBody
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
class CreateRemoteFileWithAppProviderOperation(
private val createFileWithAppProviderEndpoint: String,
private val parentContainerId: String,
private val filename: String,
) : RemoteOperation<String>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<String> {
return try {
val createFileWithAppProviderRequestBody = CreateFileWithAppProviderParams(parentContainerId, filename)
.toRequestBody()
val stringUrl = client.baseUri.toString() + WebdavUtils.encodePath(createFileWithAppProviderEndpoint)
val postMethod = PostMethod(URL(stringUrl), createFileWithAppProviderRequestBody).apply {
setReadTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
setConnectionTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
}
val status = client.executeHttpMethod(postMethod)
Timber.d("Create file $filename with app provider in folder with ID $parentContainerId - $status${if (!isSuccess(status)) "(FAIL)" else ""}")
if (isSuccess(status)) RemoteOperationResult<String>(ResultCode.OK).apply {
val moshi = Moshi.Builder().build()
val adapter: JsonAdapter<CreateFileWithAppProviderResponse> = moshi.adapter(CreateFileWithAppProviderResponse::class.java)
data = postMethod.getResponseBodyAsString()?.let { adapter.fromJson(it)!!.fileId }
}
else RemoteOperationResult<String>(postMethod).apply { data = "" }
} catch (e: Exception) {
val result = RemoteOperationResult<String>(e)
Timber.e(e, "Create file $filename with app provider in folder with ID $parentContainerId failed")
result
}
}
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK
data class CreateFileWithAppProviderParams(
val parentContainerId: String,
val filename: String,
) {
fun toRequestBody(): RequestBody =
FormBody.Builder()
.add(PARAM_PARENT_CONTAINER_ID, parentContainerId)
.add(PARAM_FILENAME, filename)
.build()
companion object {
const val PARAM_PARENT_CONTAINER_ID = "parent_container_id"
const val PARAM_FILENAME = "filename"
}
}
@JsonClass(generateAdapter = true)
data class CreateFileWithAppProviderResponse(
@Json(name = "file_id")
val fileId: String,
)
companion object {
private const val TIMEOUT: Long = 5_000
}
}

View File

@ -1,78 +0,0 @@
/* ownCloud Android Library is available under MIT license
* @author Abel García de Prada
*
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.appregistry
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK
import com.owncloud.android.lib.resources.appregistry.responses.AppRegistryResponse
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import timber.log.Timber
import java.net.URL
class GetRemoteAppRegistryOperation(private val appUrl: String?) : RemoteOperation<AppRegistryResponse>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<AppRegistryResponse> {
var result: RemoteOperationResult<AppRegistryResponse>
try {
val uriBuilder = client.baseUri.buildUpon().apply {
appendEncodedPath(appUrl)
}
val getMethod = GetMethod(URL(uriBuilder.build().toString()))
val status = client.executeHttpMethod(getMethod)
val response = getMethod.getResponseBodyAsString()
if (status == HttpConstants.HTTP_OK) {
Timber.d("Successful response $response")
// Parse the response
val moshi: Moshi = Moshi.Builder().build()
val adapter: JsonAdapter<AppRegistryResponse> = moshi.adapter(AppRegistryResponse::class.java)
val appRegistryResponse: AppRegistryResponse = response?.let { adapter.fromJson(it) } ?: AppRegistryResponse(value = emptyList())
result = RemoteOperationResult(OK)
result.data = appRegistryResponse
Timber.d("Get AppRegistry completed and parsed to ${result.data}")
} else {
result = RemoteOperationResult(getMethod)
Timber.e("Failed response while getting app registry from the server status code: $status; response message: $response")
}
} catch (e: Exception) {
result = RemoteOperationResult(e)
Timber.e(e, "Exception while getting app registry")
}
return result
}
}

View File

@ -1,107 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.appregistry
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.nonwebdav.PostMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import okhttp3.FormBody
import okhttp3.RequestBody
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
class GetUrlToOpenInWebRemoteOperation(
private val openWithWebEndpoint: String,
private val fileId: String,
private val appName: String,
) : RemoteOperation<String>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<String> {
return try {
val openInWebRequestBody = OpenInWebParams(fileId, appName).toRequestBody()
val stringUrl =
client.baseUri.toString() + WebdavUtils.encodePath(openWithWebEndpoint)
val postMethod = PostMethod(URL(stringUrl), openInWebRequestBody).apply {
setReadTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
setConnectionTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
}
val status = client.executeHttpMethod(postMethod)
Timber.d("Open in web for file: $fileId - $status${if (!isSuccess(status)) "(FAIL)" else ""}")
if (isSuccess(status)) RemoteOperationResult<String>(ResultCode.OK).apply {
val moshi = Moshi.Builder().build()
val adapter: JsonAdapter<OpenInWebResponse> = moshi.adapter(OpenInWebResponse::class.java)
data = postMethod.getResponseBodyAsString()?.let { adapter.fromJson(it)!!.uri }
}
else RemoteOperationResult<String>(postMethod).apply { data = "" }
} catch (e: Exception) {
val result = RemoteOperationResult<String>(e)
Timber.e(e, "Open in web for file: $fileId failed")
result
}
}
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK
data class OpenInWebParams(
val fileId: String,
val appName: String,
) {
fun toRequestBody(): RequestBody =
FormBody.Builder()
.add(PARAM_FILE_ID, fileId)
.add(PARAM_APP_NAME, appName)
.build()
companion object {
const val PARAM_FILE_ID = "file_id"
const val PARAM_APP_NAME = "app_name"
}
}
@JsonClass(generateAdapter = true)
data class OpenInWebResponse(val uri: String)
companion object {
/**
* Maximum time to wait for a response from the server in milliseconds.
*/
private const val TIMEOUT = 5_000L
}
}

View File

@ -1,56 +0,0 @@
/* ownCloud Android Library is available under MIT license
* @author Abel García de Prada
*
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.appregistry.responses
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class AppRegistryResponse(
@Json(name = "mime-types")
val value: List<AppRegistryMimeTypeResponse>
)
@JsonClass(generateAdapter = true)
data class AppRegistryMimeTypeResponse(
@Json(name = "mime_type") val mimeType: String,
val ext: String? = null,
@Json(name = "app_providers")
val appProviders: List<AppRegistryProviderResponse>,
val name: String? = null,
val icon: String? = null,
val description: String? = null,
@Json(name = "allow_creation")
val allowCreation: Boolean? = null,
@Json(name = "default_application")
val defaultApplication: String? = null
)
@JsonClass(generateAdapter = true)
data class AppRegistryProviderResponse(
val name: String,
val icon: String,
)

View File

@ -1,44 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.resources.appregistry.services
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.Service
import com.owncloud.android.lib.resources.appregistry.responses.AppRegistryResponse
interface AppRegistryService : Service {
fun getAppRegistry(appUrl: String?): RemoteOperationResult<AppRegistryResponse>
fun getUrlToOpenInWeb(
openWebEndpoint: String,
fileId: String,
appName: String,
): RemoteOperationResult<String>
fun createFileWithAppProvider(
createFileWithAppProviderEndpoint: String,
parentContainerId: String,
filename: String,
): RemoteOperationResult<String>
}

View File

@ -1,54 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.resources.appregistry.services
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.appregistry.CreateRemoteFileWithAppProviderOperation
import com.owncloud.android.lib.resources.appregistry.GetRemoteAppRegistryOperation
import com.owncloud.android.lib.resources.appregistry.GetUrlToOpenInWebRemoteOperation
import com.owncloud.android.lib.resources.appregistry.responses.AppRegistryResponse
class OCAppRegistryService(override val client: OwnCloudClient) : AppRegistryService {
override fun getAppRegistry(appUrl: String?): RemoteOperationResult<AppRegistryResponse> =
GetRemoteAppRegistryOperation(appUrl).execute(client)
override fun getUrlToOpenInWeb(openWebEndpoint: String, fileId: String, appName: String): RemoteOperationResult<String> =
GetUrlToOpenInWebRemoteOperation(
openWithWebEndpoint = openWebEndpoint,
fileId = fileId,
appName = appName
).execute(client)
override fun createFileWithAppProvider(
createFileWithAppProviderEndpoint: String,
parentContainerId: String,
filename: String
): RemoteOperationResult<String> =
CreateRemoteFileWithAppProviderOperation(
createFileWithAppProviderEndpoint = createFileWithAppProviderEndpoint,
parentContainerId = parentContainerId,
filename = filename,
).execute(client)
}

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -25,8 +25,9 @@ package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils.allPropSet
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils.allPropset
import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod
import com.owncloud.android.lib.common.network.RedirectionPath
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
@ -41,29 +42,41 @@ import java.util.concurrent.TimeUnit
* @author David A. Velasco
* @author David González Verdugo
* @author Abel García de Prada
* @author Juan Carlos Garrote Gascón
*
* @param remotePath Path to append to the URL owned by the client instance.
* @param isUserLoggedIn When `true`, the username won't be added at the end of the PROPFIND url since is not
* @param isUserLogged When `true`, the username won't be added at the end of the PROPFIND url since is not
* needed to check user credentials
*/
class CheckPathExistenceRemoteOperation(
val remotePath: String? = "",
val isUserLoggedIn: Boolean,
val spaceWebDavUrl: String? = null,
val isUserLogged: Boolean
) : RemoteOperation<Boolean>() {
/**
* Gets the sequence of redirections followed during the execution of the operation.
*
* @return Sequence of redirections followed, if any, or NULL if the operation was not executed.
*/
var redirectionPath: RedirectionPath? = null
private set
override fun run(client: OwnCloudClient): RemoteOperationResult<Boolean> {
val baseStringUrl = spaceWebDavUrl ?: if (isUserLoggedIn) client.userFilesWebDavUri.toString() else client.baseFilesWebDavUri.toString()
val stringUrl = if (isUserLoggedIn) baseStringUrl + WebdavUtils.encodePath(remotePath) else baseStringUrl
val previousFollowRedirects = client.followRedirects()
return try {
val propFindMethod = PropfindMethod(URL(stringUrl), 0, allPropSet).apply {
val stringUrl =
if (isUserLogged) client.baseFilesWebDavUri.toString()
else client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(remotePath)
val propFindMethod = PropfindMethod(URL(stringUrl), 0, allPropset).apply {
setReadTimeout(TIMEOUT.toLong(), TimeUnit.SECONDS)
setConnectionTimeout(TIMEOUT.toLong(), TimeUnit.SECONDS)
}
val status = client.executeHttpMethod(propFindMethod)
client.setFollowRedirects(false)
var status = client.executeHttpMethod(propFindMethod)
if (previousFollowRedirects) {
redirectionPath = client.followRedirection(propFindMethod)
status = redirectionPath?.lastStatus!!
}
/* PROPFIND method
* 404 NOT FOUND: path doesn't exist,
* 207 MULTI_STATUS: path exists.
@ -78,13 +91,19 @@ class CheckPathExistenceRemoteOperation(
val result = RemoteOperationResult<Boolean>(e)
Timber.e(
e,
"Existence check for $stringUrl : ${result.logMessage}"
"Existence check for ${client.userFilesWebDavUri}${WebdavUtils.encodePath(remotePath)} : ${result.logMessage}"
)
result.data = false
result
} finally {
client.setFollowRedirects(previousFollowRedirects)
}
}
/**
* @return 'True' if the operation was executed and at least one redirection was followed.
*/
fun wasRedirected() = redirectionPath?.redirectionsCount ?: 0 > 0
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_MULTI_STATUS
companion object {

View File

@ -0,0 +1,129 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.CopyMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import timber.log.Timber;
import java.net.URL;
import java.util.concurrent.TimeUnit;
/**
* Remote operation moving a remote file or folder in the ownCloud server to a different folder
* in the same account.
*
* Allows renaming the moving file/folder at the same time.
*
* @author David A. Velasco
* @author Christian Schabesberger
* @author David González V.
*/
public class CopyRemoteFileOperation extends RemoteOperation<String> {
private static final int COPY_READ_TIMEOUT = 600000;
private static final int COPY_CONNECTION_TIMEOUT = 5000;
private String mSrcRemotePath;
private String mTargetRemotePath;
private boolean mOverwrite;
/**
* Constructor.
* <p/>
* TODO Paths should finish in "/" in the case of folders. ?
*
* @param srcRemotePath Remote path of the file/folder to move.
* @param targetRemotePath Remove path desired for the file/folder after moving it.
*/
public CopyRemoteFileOperation(String srcRemotePath, String targetRemotePath, boolean overwrite
) {
mSrcRemotePath = srcRemotePath;
mTargetRemotePath = targetRemotePath;
mOverwrite = overwrite;
}
/**
* Performs the rename operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult<String> run(OwnCloudClient client) {
if (mTargetRemotePath.equals(mSrcRemotePath)) {
// nothing to do!
return new RemoteOperationResult<>(ResultCode.OK);
}
if (mTargetRemotePath.startsWith(mSrcRemotePath)) {
return new RemoteOperationResult<>(ResultCode.INVALID_COPY_INTO_DESCENDANT);
}
/// perform remote operation
RemoteOperationResult result;
try {
CopyMethod copyMethod =
new CopyMethod(new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mSrcRemotePath)),
client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mTargetRemotePath),
mOverwrite);
copyMethod.setReadTimeout(COPY_READ_TIMEOUT, TimeUnit.SECONDS);
copyMethod.setConnectionTimeout(COPY_CONNECTION_TIMEOUT, TimeUnit.SECONDS);
final int status = client.executeHttpMethod(copyMethod);
if (status == HttpConstants.HTTP_CREATED || status == HttpConstants.HTTP_NO_CONTENT) {
String fileRemoteId = copyMethod.getResponseHeader(HttpConstants.OC_FILE_REMOTE_ID);
result = new RemoteOperationResult<>(ResultCode.OK);
result.setData(fileRemoteId);
} else if (status == HttpConstants.HTTP_PRECONDITION_FAILED && !mOverwrite) {
result = new RemoteOperationResult<>(ResultCode.INVALID_OVERWRITE);
client.exhaustResponse(copyMethod.getResponseBodyAsStream());
/// for other errors that could be explicitly handled, check first:
/// http://www.webdav.org/specs/rfc4918.html#rfc.section.9.9.4
} else {
result = new RemoteOperationResult<>(copyMethod);
client.exhaustResponse(copyMethod.getResponseBodyAsStream());
}
Timber.i("Copy " + mSrcRemotePath + " to " + mTargetRemotePath + ": " + result.getLogMessage());
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Copy " + mSrcRemotePath + " to " + mTargetRemotePath + ": " + result.getLogMessage());
}
return result;
}
}

View File

@ -1,133 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.CopyMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.common.utils.isOneOf
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
/**
* Remote operation copying a remote file or folder in the ownCloud server to a different folder
* in the same account.
*
* Allows renaming the copying file/folder at the same time.
*
* @author David A. Velasco
* @author Christian Schabesberger
* @author David González V.
* @author Juan Carlos Garrote Gascón
* @author Manuel Plazas Palacio
*
* @param sourceRemotePath Remote path of the file/folder to copy.
* @param targetRemotePath Remote path desired for the file/folder to copy it.
*/
class CopyRemoteFileOperation(
private val sourceRemotePath: String,
private val targetRemotePath: String,
private val sourceSpaceWebDavUrl: String? = null,
private val targetSpaceWebDavUrl: String? = null,
private val forceOverride: Boolean = false,
) : RemoteOperation<String>() {
/**
* Performs the rename operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
override fun run(client: OwnCloudClient): RemoteOperationResult<String> {
if (targetRemotePath == sourceRemotePath && sourceSpaceWebDavUrl == targetSpaceWebDavUrl) {
// nothing to do!
return RemoteOperationResult(ResultCode.OK)
}
if (targetRemotePath.startsWith(sourceRemotePath) && sourceSpaceWebDavUrl == targetSpaceWebDavUrl) {
return RemoteOperationResult(ResultCode.INVALID_COPY_INTO_DESCENDANT)
}
/// perform remote operation
var result: RemoteOperationResult<String>
try {
val copyMethod = CopyMethod(
url = URL((sourceSpaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(sourceRemotePath)),
destinationUrl = (targetSpaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(targetRemotePath),
forceOverride = forceOverride,
).apply {
addRequestHeaders(this)
setReadTimeout(COPY_READ_TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(COPY_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
}
val status = client.executeHttpMethod(copyMethod)
when {
isSuccess(status) -> {
val fileRemoteId = copyMethod.getResponseHeader(HttpConstants.OC_FILE_REMOTE_ID)
result = RemoteOperationResult(ResultCode.OK)
result.setData(fileRemoteId)
}
isPreconditionFailed(status) -> {
result = RemoteOperationResult(ResultCode.INVALID_OVERWRITE)
client.exhaustResponse(copyMethod.getResponseBodyAsStream())
/// for other errors that could be explicitly handled, check first:
/// http://www.webdav.org/specs/rfc4918.html#rfc.section.9.9.4
}
else -> {
result = RemoteOperationResult(copyMethod)
client.exhaustResponse(copyMethod.getResponseBodyAsStream())
}
}
Timber.i("Copy $sourceRemotePath to $targetRemotePath: ${result.logMessage}")
} catch (e: Exception) {
result = RemoteOperationResult(e)
Timber.e(e, "Copy $sourceRemotePath to $targetRemotePath: ${result.logMessage}")
}
return result
}
private fun addRequestHeaders(copyMethod: CopyMethod) {
//Adding this because the library has an error with override
if (copyMethod.forceOverride) {
copyMethod.setRequestHeader(OVERWRITE, TRUE)
}
}
private fun isSuccess(status: Int) = status.isOneOf(HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT)
private fun isPreconditionFailed(status: Int) = status == HttpConstants.HTTP_PRECONDITION_FAILED
companion object {
private const val COPY_READ_TIMEOUT = 10L
private const val COPY_CONNECTION_TIMEOUT = 6L
private const val OVERWRITE = "overwrite"
private const val TRUE = "T"
}
}

View File

@ -0,0 +1,113 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2019 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import android.net.Uri;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.MkColMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import timber.log.Timber;
import java.net.URL;
import java.util.concurrent.TimeUnit;
/**
* Remote operation performing the creation of a new folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
*/
public class CreateRemoteFolderOperation extends RemoteOperation {
private static final int READ_TIMEOUT = 30000;
private static final int CONNECTION_TIMEOUT = 5000;
private String mRemotePath;
private boolean mCreateFullPath;
protected boolean createChunksFolder;
/**
* Constructor
*
* @param remotePath Full path to the new directory to create in the remote server.
* @param createFullPath 'True' means that all the ancestor folders should be created.
*/
public CreateRemoteFolderOperation(String remotePath, boolean createFullPath) {
mRemotePath = remotePath;
mCreateFullPath = createFullPath;
createChunksFolder = false;
}
/**
* Performs the operation
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result = createFolder(client);
if (!result.isSuccess() && mCreateFullPath &&
RemoteOperationResult.ResultCode.CONFLICT == result.getCode()) {
result = createParentFolder(FileUtils.getParentPath(mRemotePath), client);
if (result.isSuccess()) {
result = createFolder(client); // second (and last) try
}
}
return result;
}
private RemoteOperationResult createFolder(OwnCloudClient client) {
RemoteOperationResult result;
try {
Uri webDavUri = createChunksFolder ? client.getUploadsWebDavUri() : client.getUserFilesWebDavUri();
final MkColMethod mkcol = new MkColMethod(new URL(webDavUri + WebdavUtils.encodePath(mRemotePath)));
mkcol.setReadTimeout(READ_TIMEOUT, TimeUnit.SECONDS);
mkcol.setConnectionTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS);
final int status = client.executeHttpMethod(mkcol);
result = (status == HttpConstants.HTTP_CREATED)
? new RemoteOperationResult<>(ResultCode.OK)
: new RemoteOperationResult<>(mkcol);
Timber.d("Create directory " + mRemotePath + ": " + result.getLogMessage());
client.exhaustResponse(mkcol.getResponseBodyAsStream());
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Create directory " + mRemotePath + ": " + result.getLogMessage());
}
return result;
}
private RemoteOperationResult createParentFolder(String parentPath, OwnCloudClient client) {
RemoteOperation operation = new CreateRemoteFolderOperation(parentPath, mCreateFullPath);
return operation.execute(client);
}
}

View File

@ -1,110 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.MkColMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
/**
* Remote operation performing the creation of a new folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
*
* @param remotePath Full path to the new directory to create in the remote server.
* @param createFullPath 'True' means that all the ancestor folders should be created.
*/
class CreateRemoteFolderOperation(
val remotePath: String,
private val createFullPath: Boolean,
private val isChunksFolder: Boolean = false,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
var result = createFolder(client)
if (!result.isSuccess && createFullPath && result.code == ResultCode.CONFLICT) {
result = createParentFolder(FileUtils.getParentPath(remotePath), client)
if (result.isSuccess) {
// Second and last try
result = createFolder(client)
}
}
return result
}
private fun createFolder(client: OwnCloudClient): RemoteOperationResult<Unit> {
var result: RemoteOperationResult<Unit>
try {
val webDavUri = if (isChunksFolder) {
client.uploadsWebDavUri.toString()
} else {
spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
}
val mkCol = MkColMethod(
URL(webDavUri + WebdavUtils.encodePath(remotePath))
).apply {
setReadTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
}
val status = client.executeHttpMethod(mkCol)
result =
if (status == HttpConstants.HTTP_CREATED) {
RemoteOperationResult(ResultCode.OK)
} else {
RemoteOperationResult(mkCol)
}
Timber.d("Create directory $remotePath: ${result.logMessage}")
client.exhaustResponse(mkCol.getResponseBodyAsStream())
} catch (e: Exception) {
result = RemoteOperationResult(e)
Timber.e(e, "Create directory $remotePath: ${result.logMessage}")
}
return result
}
private fun createParentFolder(parentPath: String, client: OwnCloudClient): RemoteOperationResult<Unit> {
val operation: RemoteOperation<Unit> = CreateRemoteFolderOperation(parentPath, createFullPath)
return operation.execute(client)
}
companion object {
private const val READ_TIMEOUT: Long = 30_000
private const val CONNECTION_TIMEOUT: Long = 5_000
}
}

View File

@ -0,0 +1,221 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2016 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod;
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.OperationCancelledException;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import timber.log.Timber;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.net.URL;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Remote operation performing the download of a remote file in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
*/
public class DownloadRemoteFileOperation extends RemoteOperation {
private static final int FORBIDDEN_ERROR = 403;
private static final int SERVICE_UNAVAILABLE_ERROR = 503;
private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<>();
private long mModificationTimestamp = 0;
private String mEtag = "";
private GetMethod mGet;
private String mRemotePath;
private String mLocalFolderPath;
public DownloadRemoteFileOperation(String remotePath, String localFolderPath) {
mRemotePath = remotePath;
mLocalFolderPath = localFolderPath;
}
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result;
/// download will be performed to a temporal file, then moved to the final location
File tmpFile = new File(getTmpPath());
/// perform the download
try {
tmpFile.getParentFile().mkdirs();
result = downloadFile(client, tmpFile);
Timber.i("Download of " + mRemotePath + " to " + getTmpPath() + ": " + result.getLogMessage());
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Download of " + mRemotePath + " to " + getTmpPath() + ": " + result.getLogMessage());
}
return result;
}
private RemoteOperationResult downloadFile(OwnCloudClient client, File targetFile) throws
Exception {
RemoteOperationResult result;
int status;
boolean savedFile = false;
mGet = new GetMethod(new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mRemotePath)));
Iterator<OnDatatransferProgressListener> it;
FileOutputStream fos = null;
BufferedInputStream bis = null;
try {
status = client.executeHttpMethod(mGet);
if (isSuccess(status)) {
targetFile.createNewFile();
bis = new BufferedInputStream(mGet.getResponseBodyAsStream());
fos = new FileOutputStream(targetFile);
long transferred = 0;
String contentLength = mGet.getResponseHeader(HttpConstants.CONTENT_LENGTH_HEADER);
long totalToTransfer =
(contentLength != null
&& contentLength.length() > 0)
? Long.parseLong(contentLength)
: 0;
byte[] bytes = new byte[4096];
int readResult;
while ((readResult = bis.read(bytes)) != -1) {
synchronized (mCancellationRequested) {
if (mCancellationRequested.get()) {
mGet.abort();
throw new OperationCancelledException();
}
}
fos.write(bytes, 0, readResult);
transferred += readResult;
synchronized (mDataTransferListeners) {
it = mDataTransferListeners.iterator();
while (it.hasNext()) {
it.next().onTransferProgress(readResult, transferred, totalToTransfer,
targetFile.getName());
}
}
}
if (transferred == totalToTransfer) { // Check if the file is completed
savedFile = true;
final String modificationTime =
mGet.getResponseHeaders().get("Last-Modified") != null
? mGet.getResponseHeaders().get("Last-Modified")
: mGet.getResponseHeader("last-modified");
if (modificationTime != null) {
final Date d = WebdavUtils.parseResponseDate(modificationTime);
mModificationTimestamp = (d != null) ? d.getTime() : 0;
} else {
Timber.e("Could not read modification time from response downloading %s", mRemotePath);
}
mEtag = WebdavUtils.getEtagFromResponse(mGet);
// Get rid of extra quotas
mEtag = mEtag.replace("\"", "");
if (mEtag.length() == 0) {
Timber.e("Could not read eTag from response downloading %s", mRemotePath);
}
} else {
Timber.e("Content-Length not equal to transferred bytes.");
Timber.d("totalToTransfer = %d, transferred = %d", totalToTransfer, transferred);
client.exhaustResponse(mGet.getResponseBodyAsStream());
// TODO some kind of error control!
}
} else if (status != FORBIDDEN_ERROR && status != SERVICE_UNAVAILABLE_ERROR) {
client.exhaustResponse(mGet.getResponseBodyAsStream());
} // else, body read by RemoteOperationResult constructor
result = isSuccess(status)
? new RemoteOperationResult<>(RemoteOperationResult.ResultCode.OK)
: new RemoteOperationResult<>(mGet);
} finally {
if (fos != null) {
fos.close();
}
if (bis != null) {
bis.close();
}
if (!savedFile && targetFile.exists()) {
targetFile.delete();
}
}
return result;
}
private boolean isSuccess(int status) {
return (status == HttpConstants.HTTP_OK);
}
private String getTmpPath() {
return mLocalFolderPath + mRemotePath;
}
public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.add(listener);
}
}
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.remove(listener);
}
}
public void cancel() {
mCancellationRequested.set(true); // atomic set; there is no need of synchronizing it
}
public long getModificationTimestamp() {
return mModificationTimestamp;
}
public String getEtag() {
return mEtag;
}
}

View File

@ -1,185 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.OperationCancelledException
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import timber.log.Timber
import java.io.BufferedInputStream
import java.io.File
import java.io.FileOutputStream
import java.net.URL
import java.util.concurrent.atomic.AtomicBoolean
/**
* Remote operation performing the download of a remote file in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
*/
class DownloadRemoteFileOperation(
private val remotePath: String,
localFolderPath: String,
private val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() {
private val cancellationRequested = AtomicBoolean(false)
private val dataTransferListeners: MutableSet<OnDatatransferProgressListener> = HashSet()
var modificationTimestamp: Long = 0
private set
var etag: String = ""
private set
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
// download will be performed to a temporal file, then moved to the final location
val tmpFile = File(tmpPath)
// perform the download
return try {
tmpFile.parentFile?.mkdirs()
downloadFile(client, tmpFile).also { result ->
Timber.i("Download of $remotePath to $tmpPath: ${result.logMessage}")
}
} catch (e: Exception) {
RemoteOperationResult<Unit>(e).also { result ->
Timber.e(e, "Download of $remotePath to $tmpPath: ${result.logMessage}")
}
}
}
@Throws(Exception::class)
private fun downloadFile(client: OwnCloudClient, targetFile: File): RemoteOperationResult<Unit> {
val result: RemoteOperationResult<Unit>
var it: Iterator<OnDatatransferProgressListener>
var fos: FileOutputStream? = null
var bis: BufferedInputStream? = null
var savedFile = false
val webDavUri = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
val getMethod = GetMethod(URL(webDavUri + WebdavUtils.encodePath(remotePath)))
try {
val status = client.executeHttpMethod(getMethod)
if (isSuccess(status)) {
targetFile.createNewFile()
bis = BufferedInputStream(getMethod.getResponseBodyAsStream())
fos = FileOutputStream(targetFile)
var transferred: Long = 0
val contentLength = getMethod.getResponseHeader(HttpConstants.CONTENT_LENGTH_HEADER)
val totalToTransfer = if (!contentLength.isNullOrEmpty()) {
contentLength.toLong()
} else {
0
}
val bytes = ByteArray(4096)
var readResult: Int
while (bis.read(bytes).also { readResult = it } != -1) {
synchronized(cancellationRequested) {
if (cancellationRequested.get()) {
getMethod.abort()
throw OperationCancelledException()
}
}
fos.write(bytes, 0, readResult)
transferred += readResult.toLong()
synchronized(dataTransferListeners) {
it = dataTransferListeners.iterator()
while (it.hasNext()) {
it.next()
.onTransferProgress(readResult.toLong(), transferred, totalToTransfer, targetFile.name)
}
}
}
if (transferred == totalToTransfer) { // Check if the file is completed
savedFile = true
val modificationTime =
getMethod.getResponseHeaders()?.get("Last-Modified")
?: getMethod.getResponseHeader("last-modified")
if (modificationTime != null) {
val modificationDate = WebdavUtils.parseResponseDate(modificationTime)
modificationTimestamp = modificationDate?.time ?: 0
} else {
Timber.e("Could not read modification time from response downloading %s", remotePath)
}
etag = WebdavUtils.getEtagFromResponse(getMethod)
// Get rid of extra quotas
etag = etag.replace("\"", "")
if (etag.isEmpty()) {
Timber.e("Could not read eTag from response downloading %s", remotePath)
}
} else {
Timber.e("Content-Length not equal to transferred bytes.")
Timber.d("totalToTransfer = $totalToTransfer, transferred = $transferred")
client.exhaustResponse(getMethod.getResponseBodyAsStream())
// TODO some kind of error control!
}
} else if (status != HttpConstants.HTTP_FORBIDDEN && status != HttpConstants.HTTP_SERVICE_UNAVAILABLE) {
client.exhaustResponse(getMethod.getResponseBodyAsStream())
} // else, body read by RemoteOperationResult constructor
result =
if (isSuccess(status)) {
RemoteOperationResult(RemoteOperationResult.ResultCode.OK)
} else {
RemoteOperationResult(getMethod)
}
} finally {
fos?.close()
bis?.close()
if (!savedFile && targetFile.exists()) {
targetFile.delete()
}
}
return result
}
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK
private val tmpPath: String = localFolderPath + remotePath
fun addDatatransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListeners) { dataTransferListeners.add(listener) }
}
fun removeDatatransferProgressListener(listener: OnDatatransferProgressListener?) {
synchronized(dataTransferListeners) { dataTransferListeners.remove(listener) }
}
fun cancel() {
cancellationRequested.set(true) // atomic set; there is no need of synchronizing it
}
}

View File

@ -32,7 +32,6 @@ public class FileUtils {
public static final String FINAL_CHUNKS_FILE = ".file";
public static final String MIME_DIR = "DIR";
public static final String MIME_DIR_UNIX = "httpd/unix-directory";
public static final String MODE_READ_ONLY = "r";
static String getParentPath(String remotePath) {
String parentPath = new File(remotePath).getParent();

View File

@ -1,77 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils
import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
/**
* Operation to get the base url, which might differ in case of a redirect.
*
* @author Christian Schabesberger
*/
class GetBaseUrlRemoteOperation : RemoteOperation<String?>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<String?> {
return try {
val stringUrl = client.baseFilesWebDavUri.toString()
val propFindMethod = PropfindMethod(URL(stringUrl), 0, DavUtils.allPropSet).apply {
setReadTimeout(TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(TIMEOUT, TimeUnit.SECONDS)
}
val status = client.executeHttpMethod(propFindMethod)
if (isSuccess(status)) {
RemoteOperationResult<String?>(RemoteOperationResult.ResultCode.OK).apply {
data = propFindMethod.getFinalUrl().toString()
}
} else {
RemoteOperationResult<String?>(propFindMethod).apply {
data = null
}
}
} catch (e: Exception) {
Timber.e(e, "Could not get actuall (or redirected) base URL from base url (/).")
RemoteOperationResult<String?>(e)
}
}
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_MULTI_STATUS
companion object {
/**
* Maximum time to wait for a response from the server in milliseconds.
*/
private const val TIMEOUT = 10_000L
}
}

View File

@ -0,0 +1,146 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import android.net.Uri;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import timber.log.Timber;
import java.net.URL;
import java.util.concurrent.TimeUnit;
/**
* Remote operation moving a remote file or folder in the ownCloud server to a different folder
* in the same account.
* <p>
* Allows renaming the moving file/folder at the same time.
*
* @author David A. Velasco
* @author David González Verdugo
*/
public class MoveRemoteFileOperation extends RemoteOperation {
private static final int MOVE_READ_TIMEOUT = 600000;
private static final int MOVE_CONNECTION_TIMEOUT = 5000;
private String mSrcRemotePath;
private String mTargetRemotePath;
private boolean mOverwrite;
protected boolean moveChunkedFile = false;
protected String mFileLastModifTimestamp;
protected long mFileLength;
/**
* Constructor.
* <p>
* TODO Paths should finish in "/" in the case of folders. ?
*
* @param srcRemotePath Remote path of the file/folder to move.
* @param targetRemotePath Remote path desired for the file/folder after moving it.
*/
public MoveRemoteFileOperation(String srcRemotePath,
String targetRemotePath,
boolean overwrite) {
mSrcRemotePath = srcRemotePath;
mTargetRemotePath = targetRemotePath;
mOverwrite = overwrite;
}
/**
* Performs the rename operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
if (mTargetRemotePath.equals(mSrcRemotePath)) {
// nothing to do!
return new RemoteOperationResult<>(ResultCode.OK);
}
if (mTargetRemotePath.startsWith(mSrcRemotePath)) {
return new RemoteOperationResult<>(ResultCode.INVALID_MOVE_INTO_DESCENDANT);
}
/// perform remote operation
RemoteOperationResult result;
try {
// After finishing a chunked upload, we have to move the resulting file from uploads folder to files one,
// so this uri has to be customizable
Uri srcWebDavUri = moveChunkedFile ? client.getUploadsWebDavUri() : client.getUserFilesWebDavUri();
final MoveMethod move = new MoveMethod(
new URL(srcWebDavUri + WebdavUtils.encodePath(mSrcRemotePath)),
client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mTargetRemotePath),
mOverwrite);
if (moveChunkedFile) {
move.addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, mFileLastModifTimestamp);
move.addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, String.valueOf(mFileLength));
}
move.setReadTimeout(MOVE_READ_TIMEOUT, TimeUnit.SECONDS);
move.setConnectionTimeout(MOVE_CONNECTION_TIMEOUT, TimeUnit.SECONDS);
final int status = client.executeHttpMethod(move);
/// process response
if (isSuccess(status)) {
result = new RemoteOperationResult<>(ResultCode.OK);
} else if (status == HttpConstants.HTTP_PRECONDITION_FAILED && !mOverwrite) {
result = new RemoteOperationResult<>(ResultCode.INVALID_OVERWRITE);
client.exhaustResponse(move.getResponseBodyAsStream());
/// for other errors that could be explicitly handled, check first:
/// http://www.webdav.org/specs/rfc4918.html#rfc.section.9.9.4
} else {
result = new RemoteOperationResult<>(move);
client.exhaustResponse(move.getResponseBodyAsStream());
}
Timber.i("Move " + mSrcRemotePath + " to " + mTargetRemotePath + ": " + result.getLogMessage());
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Move " + mSrcRemotePath + " to " + mTargetRemotePath + ": " + result.getLogMessage());
}
return result;
}
protected boolean isSuccess(int status) {
return status == HttpConstants.HTTP_CREATED || status == HttpConstants.HTTP_NO_CONTENT;
}
}

View File

@ -1,149 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.common.utils.isOneOf
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
/**
* Remote operation moving a remote file or folder in the ownCloud server to a different folder
* in the same account and space.
*
* Allows renaming the moving file/folder at the same time.
*
* @author David A. Velasco
* @author David González Verdugo
* @author Abel García de Prada
* @author Juan Carlos Garrote Gascón
* @author Manuel Plazas Palacio
*
* @param sourceRemotePath Remote path of the file/folder to copy.
* @param targetRemotePath Remote path desired for the file/folder to copy it.
*/
open class MoveRemoteFileOperation(
private val sourceRemotePath: String,
private val targetRemotePath: String,
private val spaceWebDavUrl: String? = null,
private val forceOverride: Boolean = false,
) : RemoteOperation<Unit>() {
/**
* Performs the rename operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
if (targetRemotePath == sourceRemotePath) {
// nothing to do!
return RemoteOperationResult(ResultCode.OK)
}
if (targetRemotePath.startsWith(sourceRemotePath)) {
return RemoteOperationResult(ResultCode.INVALID_MOVE_INTO_DESCENDANT)
}
/// perform remote operation
var result: RemoteOperationResult<Unit>
try {
// After finishing a chunked upload, we have to move the resulting file from uploads folder to files one,
// so this uri has to be customizable
val srcWebDavUri = getSrcWebDavUriForClient(client)
val moveMethod = MoveMethod(
url = URL((spaceWebDavUrl ?: srcWebDavUri.toString()) + WebdavUtils.encodePath(sourceRemotePath)),
destinationUrl = (spaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(targetRemotePath),
forceOverride = forceOverride,
).apply {
addRequestHeaders(this)
setReadTimeout(MOVE_READ_TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(MOVE_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
}
val status = client.executeHttpMethod(moveMethod)
when {
isSuccess(status) -> {
result = RemoteOperationResult<Unit>(ResultCode.OK)
}
isPreconditionFailed(status) -> {
result = RemoteOperationResult<Unit>(ResultCode.INVALID_OVERWRITE)
client.exhaustResponse(moveMethod.getResponseBodyAsStream())
/// for other errors that could be explicitly handled, check first:
/// http://www.webdav.org/specs/rfc4918.html#rfc.section.9.9.4
}
else -> {
result = RemoteOperationResult<Unit>(moveMethod)
client.exhaustResponse(moveMethod.getResponseBodyAsStream())
}
}
Timber.i("Move $sourceRemotePath to $targetRemotePath: ${result.logMessage}")
} catch (e: Exception) {
result = RemoteOperationResult<Unit>(e)
Timber.e(e, "Move $sourceRemotePath to $targetRemotePath: ${result.logMessage}")
}
return result
}
/**
* For standard moves, we will use [OwnCloudClient.getUserFilesWebDavUri].
* In case we need a different source Uri, override this method.
*/
open fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.userFilesWebDavUri
/**
* For standard moves, we won't need any special headers.
* In case new headers are needed, override this method
*/
open fun addRequestHeaders(moveMethod: MoveMethod) {
//Adding this because the library has an error with override
if (moveMethod.forceOverride) {
moveMethod.setRequestHeader(OVERWRITE, TRUE)
}
}
private fun isSuccess(status: Int) = status.isOneOf(HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT)
private fun isPreconditionFailed(status: Int) = status == HttpConstants.HTTP_PRECONDITION_FAILED
companion object {
private const val MOVE_READ_TIMEOUT = 10L
private const val MOVE_CONNECTION_TIMEOUT = 6L
private const val OVERWRITE = "overwrite"
private const val TRUE = "T"
}
}

View File

@ -0,0 +1,107 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2016 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils;
import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import timber.log.Timber;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import static com.owncloud.android.lib.common.http.methods.webdav.DavConstants.DEPTH_0;
import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK;
/**
* Remote operation performing the read a file from the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
*/
public class ReadRemoteFileOperation extends RemoteOperation<RemoteFile> {
private static final int SYNC_READ_TIMEOUT = 40000;
private static final int SYNC_CONNECTION_TIMEOUT = 5000;
private String mRemotePath;
/**
* Constructor
*
* @param remotePath Remote path of the file.
*/
public ReadRemoteFileOperation(String remotePath) {
mRemotePath = remotePath;
}
/**
* Performs the read operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult<RemoteFile> run(OwnCloudClient client) {
PropfindMethod propfind;
RemoteOperationResult<RemoteFile> result;
/// take the duty of check the server for the current state of the file there
try {
// remote request
propfind = new PropfindMethod(new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mRemotePath)),
DEPTH_0,
DavUtils.getAllPropset());
propfind.setReadTimeout(SYNC_READ_TIMEOUT, TimeUnit.SECONDS);
propfind.setConnectionTimeout(SYNC_CONNECTION_TIMEOUT, TimeUnit.SECONDS);
final int status = client.executeHttpMethod(propfind);
if (status == HttpConstants.HTTP_MULTI_STATUS
|| status == HttpConstants.HTTP_OK) {
final RemoteFile file = new RemoteFile(propfind.getRoot(), AccountUtils.getUserId(mAccount, mContext));
result = new RemoteOperationResult<>(OK);
result.setData(file);
} else {
result = new RemoteOperationResult<>(propfind);
client.exhaustResponse(propfind.getResponseBodyAsStream());
}
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Synchronizing file %s", mRemotePath);
}
return result;
}
}

View File

@ -1,107 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2016 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.accounts.AccountUtils
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_MULTI_STATUS
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_OK
import com.owncloud.android.lib.common.http.methods.webdav.DavConstants.DEPTH_0
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils
import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.isOneOf
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
/**
* Remote operation performing the read a file from the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
*/
class ReadRemoteFileOperation(
val remotePath: String,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<RemoteFile>() {
/**
* Performs the read operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
override fun run(client: OwnCloudClient): RemoteOperationResult<RemoteFile> {
try {
val propFind = PropfindMethod(
url = getFinalWebDavUrl(),
depth = DEPTH_0,
propertiesToRequest = DavUtils.allPropSet
).apply {
setReadTimeout(SYNC_READ_TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(SYNC_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
}
val status = client.executeHttpMethod(propFind)
Timber.i("Read remote file $remotePath with status ${propFind.statusCode}")
return if (isSuccess(status)) {
val remoteFile = RemoteFile.getRemoteFileFromDav(
davResource = propFind.root!!,
userId = AccountUtils.getUserId(mAccount, mContext),
userName = mAccount.name,
spaceWebDavUrl = spaceWebDavUrl,
)
RemoteOperationResult<RemoteFile>(RemoteOperationResult.ResultCode.OK).apply {
data = remoteFile
}
} else {
RemoteOperationResult<RemoteFile>(propFind).also {
client.exhaustResponse(propFind.getResponseBodyAsStream())
}
}
} catch (exception: Exception) {
return RemoteOperationResult(exception)
}
}
private fun getFinalWebDavUrl(): URL {
val baseWebDavUrl = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
return URL(baseWebDavUrl + WebdavUtils.encodePath(remotePath))
}
private fun isSuccess(status: Int) = status.isOneOf(HTTP_MULTI_STATUS, HTTP_OK)
companion object {
private const val SYNC_READ_TIMEOUT = 40_000L
private const val SYNC_CONNECTION_TIMEOUT = 5_000L
}
}

View File

@ -0,0 +1,127 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import at.bitfire.dav4jvm.Response;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.DavConstants;
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils;
import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import timber.log.Timber;
import java.net.URL;
import java.util.ArrayList;
import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK;
/**
* Remote operation performing the read of remote file or folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
*/
public class ReadRemoteFolderOperation extends RemoteOperation<ArrayList<RemoteFile>> {
private String mRemotePath;
/**
* Constructor
*
* @param remotePath Remote path of the file.
*/
public ReadRemoteFolderOperation(String remotePath) {
mRemotePath = remotePath;
}
/**
* Performs the read operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult<ArrayList<RemoteFile>> run(OwnCloudClient client) {
RemoteOperationResult<ArrayList<RemoteFile>> result = null;
try {
PropfindMethod propfindMethod = new PropfindMethod(
new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mRemotePath)),
DavConstants.DEPTH_1,
DavUtils.getAllPropset());
client.setFollowRedirects(true);
int status = client.executeHttpMethod(propfindMethod);
if (isSuccess(status)) {
ArrayList<RemoteFile> mFolderAndFiles = new ArrayList<>();
// parse data from remote folder
mFolderAndFiles.add(
new RemoteFile(propfindMethod.getRoot(), AccountUtils.getUserId(mAccount, mContext))
);
// loop to update every child
for (Response resource : propfindMethod.getMembers()) {
RemoteFile file = new RemoteFile(resource, AccountUtils.getUserId(mAccount, mContext));
mFolderAndFiles.add(file);
}
// Result of the operation
result = new RemoteOperationResult<>(OK);
result.setData(mFolderAndFiles);
} else { // synchronization failed
result = new RemoteOperationResult<>(propfindMethod);
}
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
} finally {
if (result == null) {
Timber.e("Synchronized " + mRemotePath + ": result is null");
} else if (result.isSuccess()) {
Timber.i("Synchronized " + mRemotePath + ": " + result.getLogMessage());
} else {
if (result.isException()) {
Timber.e(result.getException(), "Synchronized " + mRemotePath + ": " + result.getLogMessage());
} else {
Timber.e("Synchronized " + mRemotePath + ": " + result.getLogMessage());
}
}
}
return result;
}
private boolean isSuccess(int status) {
return status == HttpConstants.HTTP_MULTI_STATUS || status == HttpConstants.HTTP_OK;
}
}

View File

@ -1,118 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import at.bitfire.dav4jvm.PropertyRegistry
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.accounts.AccountUtils
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_MULTI_STATUS
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_OK
import com.owncloud.android.lib.common.http.methods.webdav.DavConstants
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils
import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod
import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.common.utils.isOneOf
import timber.log.Timber
import java.net.URL
/**
* Remote operation performing the read of remote file or folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
*/
class ReadRemoteFolderOperation(
val remotePath: String,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<ArrayList<RemoteFile>>() {
/**
* Performs the read operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
override fun run(client: OwnCloudClient): RemoteOperationResult<ArrayList<RemoteFile>> {
try {
PropertyRegistry.register(OCShareTypes.Factory())
val propfindMethod = PropfindMethod(
getFinalWebDavUrl(),
DavConstants.DEPTH_1,
DavUtils.allPropSet
)
val status = client.executeHttpMethod(propfindMethod)
if (isSuccess(status)) {
val mFolderAndFiles = ArrayList<RemoteFile>()
val remoteFolder = RemoteFile.getRemoteFileFromDav(
davResource = propfindMethod.root!!,
userId = AccountUtils.getUserId(mAccount, mContext),
userName = mAccount.name,
spaceWebDavUrl = spaceWebDavUrl,
)
mFolderAndFiles.add(remoteFolder)
// loop to update every child
propfindMethod.members.forEach { resource ->
val remoteFile = RemoteFile.getRemoteFileFromDav(
davResource = resource,
userId = AccountUtils.getUserId(mAccount, mContext),
userName = mAccount.name,
spaceWebDavUrl = spaceWebDavUrl,
)
mFolderAndFiles.add(remoteFile)
}
// Result of the operation
return RemoteOperationResult<ArrayList<RemoteFile>>(ResultCode.OK).apply {
data = mFolderAndFiles
Timber.i("Synchronized $remotePath with ${mFolderAndFiles.size} files. ${this.logMessage}")
}
} else { // synchronization failed
return RemoteOperationResult<ArrayList<RemoteFile>>(propfindMethod).also {
Timber.w("Synchronized $remotePath ${it.logMessage}")
}
}
} catch (e: Exception) {
return RemoteOperationResult<ArrayList<RemoteFile>>(e).also {
Timber.e(it.exception, "Synchronized $remotePath")
}
}
}
private fun getFinalWebDavUrl(): URL {
val baseWebDavUrl = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
return URL(baseWebDavUrl + WebdavUtils.encodePath(remotePath))
}
private fun isSuccess(status: Int): Boolean = status.isOneOf(HTTP_OK, HTTP_MULTI_STATUS)
}

View File

@ -0,0 +1,322 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import android.os.Parcel;
import android.os.Parcelable;
import at.bitfire.dav4jvm.Property;
import at.bitfire.dav4jvm.Response;
import at.bitfire.dav4jvm.property.CreationDate;
import at.bitfire.dav4jvm.property.GetContentLength;
import at.bitfire.dav4jvm.property.GetContentType;
import at.bitfire.dav4jvm.property.GetETag;
import at.bitfire.dav4jvm.property.GetLastModified;
import at.bitfire.dav4jvm.property.OCId;
import at.bitfire.dav4jvm.property.OCPermissions;
import at.bitfire.dav4jvm.property.OCPrivatelink;
import at.bitfire.dav4jvm.property.OCSize;
import at.bitfire.dav4jvm.property.QuotaAvailableBytes;
import at.bitfire.dav4jvm.property.QuotaUsedBytes;
import java.io.File;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
* Contains the data of a Remote File from a WebDavEntry
*
* @author masensio
* @author Christian Schabesberger
*/
public class RemoteFile implements Parcelable, Serializable {
/**
* Parcelable Methods
*/
public static final Parcelable.Creator<RemoteFile> CREATOR = new Parcelable.Creator<RemoteFile>() {
@Override
public RemoteFile createFromParcel(Parcel source) {
return new RemoteFile(source);
}
@Override
public RemoteFile[] newArray(int size) {
return new RemoteFile[size];
}
};
/**
* Generated - should be refreshed every time the class changes!!
*/
private static final long serialVersionUID = -8965995357413958539L;
private String mRemotePath;
private String mMimeType;
private long mLength;
private long mCreationTimestamp;
private long mModifiedTimestamp;
private String mEtag;
private String mPermissions;
private String mRemoteId;
private long mSize;
private BigDecimal mQuotaUsedBytes;
private BigDecimal mQuotaAvailableBytes;
private String mPrivateLink;
public RemoteFile() {
resetData();
}
/**
* Create new {@link RemoteFile} with given path.
* <p>
* The path received must be URL-decoded. Path separator must be File.separator, and it must be the first
* character in 'path'.
*
* @param path The remote path of the file.
*/
public RemoteFile(String path) {
resetData();
if (path == null || path.length() <= 0 || !path.startsWith(File.separator)) {
throw new IllegalArgumentException("Trying to create a OCFile with a non valid remote path: " + path);
}
mRemotePath = path;
mCreationTimestamp = 0;
mLength = 0;
mMimeType = FileUtils.MIME_DIR;
mQuotaUsedBytes = BigDecimal.ZERO;
mQuotaAvailableBytes = BigDecimal.ZERO;
mPrivateLink = null;
}
public RemoteFile(final Response davResource, String userId) {
this(RemoteFileUtil.Companion.getRemotePathFromUrl(davResource.getHref(), userId));
final List<Property> properties = davResource.getProperties();
for (Property property : properties) {
if (property instanceof CreationDate) {
this.setCreationTimestamp(
Long.parseLong(((CreationDate) property).getCreationDate()));
}
if (property instanceof GetContentLength) {
this.setLength(((GetContentLength) property).getContentLength());
}
if (property instanceof GetContentType) {
this.setMimeType(((GetContentType) property).getType());
}
if (property instanceof GetLastModified) {
this.setModifiedTimestamp(((GetLastModified) property).getLastModified());
}
if (property instanceof GetETag) {
this.setEtag(((GetETag) property).getETag());
}
if (property instanceof OCPermissions) {
this.setPermissions(((OCPermissions) property).getPermission());
}
if (property instanceof OCId) {
this.setRemoteId(((OCId) property).getId());
}
if (property instanceof OCSize) {
this.setSize(((OCSize) property).getSize());
}
if (property instanceof QuotaUsedBytes) {
this.setQuotaUsedBytes(
BigDecimal.valueOf(((QuotaUsedBytes) property).getQuotaUsedBytes()));
}
if (property instanceof QuotaAvailableBytes) {
this.setQuotaAvailableBytes(
BigDecimal.valueOf(((QuotaAvailableBytes) property).getQuotaAvailableBytes()));
}
if (property instanceof OCPrivatelink) {
this.setPrivateLink(((OCPrivatelink) property).getLink());
}
}
}
/**
* Reconstruct from parcel
*
* @param source The source parcel
*/
protected RemoteFile(Parcel source) {
readFromParcel(source);
}
/**
* Use this to find out if this file is a folder.
*
* @return true if it is a folder
*/
public boolean isFolder() {
return mMimeType != null && (mMimeType.equals(FileUtils.MIME_DIR) || mMimeType.equals(FileUtils.MIME_DIR_UNIX));
}
/**
* Getters and Setters
*/
public String getRemotePath() {
return mRemotePath;
}
public void setRemotePath(String remotePath) {
this.mRemotePath = remotePath;
}
public String getMimeType() {
return mMimeType;
}
public void setMimeType(String mimeType) {
this.mMimeType = mimeType;
}
public long getLength() {
return mLength;
}
public void setLength(long length) {
this.mLength = length;
}
public long getCreationTimestamp() {
return mCreationTimestamp;
}
public void setCreationTimestamp(long creationTimestamp) {
this.mCreationTimestamp = creationTimestamp;
}
public long getModifiedTimestamp() {
return mModifiedTimestamp;
}
public void setModifiedTimestamp(long modifiedTimestamp) {
this.mModifiedTimestamp = modifiedTimestamp;
}
public String getEtag() {
return mEtag;
}
public void setEtag(String etag) {
this.mEtag = etag;
}
public String getPermissions() {
return mPermissions;
}
public void setPermissions(String permissions) {
this.mPermissions = permissions;
}
public String getRemoteId() {
return mRemoteId;
}
public void setRemoteId(String remoteId) {
this.mRemoteId = remoteId;
}
public long getSize() {
return mSize;
}
public void setSize(long size) {
mSize = size;
}
public void setQuotaUsedBytes(BigDecimal quotaUsedBytes) {
mQuotaUsedBytes = quotaUsedBytes;
}
public void setQuotaAvailableBytes(BigDecimal quotaAvailableBytes) {
mQuotaAvailableBytes = quotaAvailableBytes;
}
public String getPrivateLink() {
return mPrivateLink;
}
public void setPrivateLink(String privateLink) {
mPrivateLink = privateLink;
}
/**
* Used internally. Reset all file properties
*/
private void resetData() {
mRemotePath = null;
mMimeType = null;
mLength = 0;
mCreationTimestamp = 0;
mModifiedTimestamp = 0;
mEtag = null;
mPermissions = null;
mRemoteId = null;
mSize = 0;
mQuotaUsedBytes = null;
mQuotaAvailableBytes = null;
mPrivateLink = null;
}
public void readFromParcel(Parcel source) {
mRemotePath = source.readString();
mMimeType = source.readString();
mLength = source.readLong();
mCreationTimestamp = source.readLong();
mModifiedTimestamp = source.readLong();
mEtag = source.readString();
mPermissions = source.readString();
mRemoteId = source.readString();
mSize = source.readLong();
mQuotaUsedBytes = (BigDecimal) source.readSerializable();
mQuotaAvailableBytes = (BigDecimal) source.readSerializable();
mPrivateLink = source.readString();
}
@Override
public int describeContents() {
return this.hashCode();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mRemotePath);
dest.writeString(mMimeType);
dest.writeLong(mLength);
dest.writeLong(mCreationTimestamp);
dest.writeLong(mModifiedTimestamp);
dest.writeString(mEtag);
dest.writeString(mPermissions);
dest.writeString(mRemoteId);
dest.writeLong(mSize);
dest.writeSerializable(mQuotaUsedBytes);
dest.writeSerializable(mQuotaAvailableBytes);
dest.writeString(mPrivateLink);
}
}

View File

@ -1,194 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import android.net.Uri
import android.os.Parcelable
import androidx.annotation.VisibleForTesting
import at.bitfire.dav4jvm.PropStat
import at.bitfire.dav4jvm.Property
import at.bitfire.dav4jvm.Response
import at.bitfire.dav4jvm.property.CreationDate
import at.bitfire.dav4jvm.property.GetContentLength
import at.bitfire.dav4jvm.property.GetContentType
import at.bitfire.dav4jvm.property.GetETag
import at.bitfire.dav4jvm.property.GetLastModified
import at.bitfire.dav4jvm.property.OCId
import at.bitfire.dav4jvm.property.OCPermissions
import at.bitfire.dav4jvm.property.OCPrivatelink
import at.bitfire.dav4jvm.property.OCSize
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes
import com.owncloud.android.lib.common.utils.isOneOf
import com.owncloud.android.lib.resources.shares.ShareType
import com.owncloud.android.lib.resources.shares.ShareType.Companion.fromValue
import kotlinx.parcelize.Parcelize
import okhttp3.HttpUrl
import timber.log.Timber
import java.io.File
/**
* Contains the data of a Remote File from a WebDavEntry
*
* The path received must be URL-decoded. Path separator must be File.separator, and it must be the first character in 'path'.
*
* @author masensio
* @author Christian Schabesberger
* @author Abel García de Prada
*/
@Parcelize
data class RemoteFile(
var remotePath: String,
var mimeType: String = "DIR",
var length: Long = 0,
var creationTimestamp: Long = 0,
var modifiedTimestamp: Long = 0,
var etag: String? = null,
var permissions: String? = null,
var remoteId: String? = null,
var size: Long = 0,
var privateLink: String? = null,
var owner: String,
var sharedByLink: Boolean = false,
var sharedWithSharee: Boolean = false,
) : Parcelable {
// TODO: Quotas not used. Use or remove them.
init {
require(
!(remotePath.isEmpty() || !remotePath.startsWith(File.separator))
) { "Trying to create a OCFile with a non valid remote path: $remotePath" }
}
/**
* Use this to find out if this file is a folder.
*
* @return true if it is a folder
*/
val isFolder
get() = mimeType.isOneOf(MIME_DIR, MIME_DIR_UNIX)
companion object {
const val MIME_DIR = "DIR"
const val MIME_DIR_UNIX = "httpd/unix-directory"
fun getRemoteFileFromDav(
davResource: Response,
userId: String,
userName: String,
spaceWebDavUrl: String? = null
): RemoteFile {
val remotePath = getRemotePathFromUrl(davResource.href, userId, spaceWebDavUrl)
val remoteFile = RemoteFile(remotePath = remotePath, owner = userName)
val properties = getPropertiesEvenIfPostProcessing(davResource)
for (property in properties) {
when (property) {
is CreationDate -> {
remoteFile.creationTimestamp = property.creationDate.toLong()
}
is GetContentLength -> {
remoteFile.length = property.contentLength
}
is GetContentType -> {
property.type?.let { remoteFile.mimeType = it }
}
is GetLastModified -> {
remoteFile.modifiedTimestamp = property.lastModified
}
is GetETag -> {
remoteFile.etag = property.eTag
}
is OCPermissions -> {
remoteFile.permissions = property.permission
}
is OCId -> {
remoteFile.remoteId = property.id
}
is OCSize -> {
remoteFile.size = property.size
}
is OCPrivatelink -> {
remoteFile.privateLink = property.link
}
is OCShareTypes -> {
val list = property.shareTypes
for (i in list.indices) {
val shareType = fromValue(list[i].toInt())
if (shareType == null) {
Timber.d("Illegal share type value: " + list[i])
continue
}
if (shareType == ShareType.PUBLIC_LINK) {
remoteFile.sharedByLink = true
} else if (shareType == ShareType.USER || shareType == ShareType.FEDERATED || shareType == ShareType.GROUP) {
remoteFile.sharedWithSharee = true
}
}
}
}
}
return remoteFile
}
/**
* Retrieves a relative path from a remote file url
*
*
* Example legacy:
* /remote.php/dav/files/username/Documents/text.txt => /Documents/text.txt
*
* Example spaces:
* /dav/spaces/8871f4f3-fc6f-4a66-8bed-62f175f76f38$05bca744-d89f-4e9c-a990-25a0d7f03fe9/Documents/text.txt => /Documents/text.txt
*
* @param url remote file url
* @param userId file owner
* @param spaceWebDavUrl custom web dav url for space
* @return remote relative path of the file
*/
@VisibleForTesting
fun getRemotePathFromUrl(
url: HttpUrl,
userId: String,
spaceWebDavUrl: String? = null,
): String {
val davFilesPath = spaceWebDavUrl ?: (OwnCloudClient.WEBDAV_FILES_PATH_4_0 + userId)
val absoluteDavPath = if (spaceWebDavUrl != null) Uri.decode(url.toString()) else Uri.decode(url.encodedPath)
val pathToOc = absoluteDavPath.split(davFilesPath).first()
return absoluteDavPath.replace(pathToOc + davFilesPath, "")
}
private fun getPropertiesEvenIfPostProcessing(response: Response): List<Property> {
return if (response.isSuccess())
response.propstat.filter { propStat -> propStat.isSuccessOrPostProcessing() }.map { it.properties }.flatten()
else
emptyList()
}
private fun PropStat.isSuccessOrPostProcessing() = (status.code / 100 == 2 || status.code == HttpConstants.HTTP_TOO_EARLY)
}
}

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -19,16 +19,31 @@
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.spaces.services
package com.owncloud.android.lib.resources.files
import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.spaces.GetRemoteSpacesOperation
import com.owncloud.android.lib.resources.spaces.responses.SpaceResponse
import okhttp3.HttpUrl
class OCSpacesService(override val client: OwnCloudClient) : SpacesService {
override fun getSpaces(): RemoteOperationResult<List<SpaceResponse>> {
return GetRemoteSpacesOperation().execute(client)
class RemoteFileUtil {
companion object {
/**
* Retrieves a relative path from a remote file url
*
*
* Example: url:port/remote.php/dav/files/username/Documents/text.txt => /Documents/text.txt
*
* @param url remote file url
* @param userId file owner
* @return remote relative path of the file
*/
fun getRemotePathFromUrl(url: HttpUrl, userId: String): String? {
val davFilesPath = OwnCloudClient.WEBDAV_FILES_PATH_4_0 + userId
val absoluteDavPath = Uri.decode(url.encodedPath)
val pathToOc = absoluteDavPath.split(davFilesPath)[0]
return absoluteDavPath.replace(pathToOc + davFilesPath, "")
}
}
}

View File

@ -0,0 +1,96 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import android.net.Uri;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.nonwebdav.DeleteMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import timber.log.Timber;
import java.net.URL;
import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK;
/**
* Remote operation performing the removal of a remote file or folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
*/
public class RemoveRemoteFileOperation extends RemoteOperation {
private String mRemotePath;
protected boolean removeChunksFolder = false;
/**
* Constructor
*
* @param remotePath RemotePath of the remote file or folder to remove from the server
*/
public RemoveRemoteFileOperation(String remotePath) {
mRemotePath = remotePath;
}
/**
* Performs the rename operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result;
try {
Uri srcWebDavUri = removeChunksFolder ? client.getUploadsWebDavUri() : client.getUserFilesWebDavUri();
DeleteMethod deleteMethod = new DeleteMethod(
new URL(srcWebDavUri + WebdavUtils.encodePath(mRemotePath)));
int status = client.executeHttpMethod(deleteMethod);
result = isSuccess(status) ?
new RemoteOperationResult<>(OK) :
new RemoteOperationResult<>(deleteMethod);
Timber.i("Remove " + mRemotePath + ": " + result.getLogMessage());
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Remove " + mRemotePath + ": " + result.getLogMessage());
}
return result;
}
private boolean isSuccess(int status) {
return status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_NO_CONTENT;
}
}

View File

@ -1,81 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_NO_CONTENT
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_OK
import com.owncloud.android.lib.common.http.methods.nonwebdav.DeleteMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.common.utils.isOneOf
import timber.log.Timber
import java.net.URL
/**
* Remote operation performing the removal of a remote file or folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
* @author Abel García de Prada
*/
open class RemoveRemoteFileOperation(
private val remotePath: String,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
var result: RemoteOperationResult<Unit>
try {
val srcWebDavUri = getSrcWebDavUriForClient(client)
val deleteMethod = DeleteMethod(
URL(srcWebDavUri + WebdavUtils.encodePath(remotePath))
)
val status = client.executeHttpMethod(deleteMethod)
result = if (isSuccess(status)) {
RemoteOperationResult<Unit>(ResultCode.OK)
} else {
RemoteOperationResult<Unit>(deleteMethod)
}
Timber.i("Remove $remotePath: ${result.logMessage}")
} catch (e: Exception) {
result = RemoteOperationResult<Unit>(e)
Timber.e(e, "Remove $remotePath: ${result.logMessage}")
}
return result
}
/**
* For standard removals, we will use [OwnCloudClient.getUserFilesWebDavUri].
* In case we need a different source Uri, override this method.
*/
open fun getSrcWebDavUriForClient(client: OwnCloudClient): String = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
private fun isSuccess(status: Int) = status.isOneOf(HTTP_OK, HTTP_NO_CONTENT)
}

View File

@ -0,0 +1,130 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2019 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import timber.log.Timber;
import java.io.File;
import java.net.URL;
import java.util.concurrent.TimeUnit;
/**
* Remote operation performing the rename of a remote file or folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
*/
public class RenameRemoteFileOperation extends RemoteOperation {
private static final int RENAME_READ_TIMEOUT = 600000;
private static final int RENAME_CONNECTION_TIMEOUT = 5000;
private String mOldName;
private String mOldRemotePath;
private String mNewName;
private String mNewRemotePath;
/**
* Constructor
*
* @param oldName Old name of the file.
* @param oldRemotePath Old remote path of the file.
* @param newName New name to set as the name of file.
* @param isFolder 'true' for folder and 'false' for files
*/
public RenameRemoteFileOperation(String oldName, String oldRemotePath, String newName,
boolean isFolder) {
mOldName = oldName;
mOldRemotePath = oldRemotePath;
mNewName = newName;
String parent = (new File(mOldRemotePath)).getParent();
parent = (parent.endsWith(File.separator)) ? parent : parent + File.separator;
mNewRemotePath = parent + mNewName;
if (isFolder) {
mNewRemotePath += File.separator;
}
}
/**
* Performs the rename operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
try {
if (mNewName.equals(mOldName)) {
return new RemoteOperationResult<>(ResultCode.OK);
}
if (targetPathIsUsed(client)) {
return new RemoteOperationResult<>(ResultCode.INVALID_OVERWRITE);
}
final MoveMethod move = new MoveMethod(new URL(client.getUserFilesWebDavUri() +
WebdavUtils.encodePath(mOldRemotePath)),
client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mNewRemotePath), false);
move.setReadTimeout(RENAME_READ_TIMEOUT, TimeUnit.SECONDS);
move.setConnectionTimeout(RENAME_READ_TIMEOUT, TimeUnit.SECONDS);
final int status = client.executeHttpMethod(move);
final RemoteOperationResult result =
(status == HttpConstants.HTTP_CREATED || status == HttpConstants.HTTP_NO_CONTENT)
? new RemoteOperationResult<>(ResultCode.OK)
: new RemoteOperationResult<>(move);
Timber.i("Rename " + mOldRemotePath + " to " + mNewRemotePath + ": " + result.getLogMessage());
client.exhaustResponse(move.getResponseBodyAsStream());
return result;
} catch (Exception e) {
final RemoteOperationResult result = new RemoteOperationResult<>(e);
Timber.e(e,
"Rename " + mOldRemotePath + " to " + ((mNewRemotePath == null) ? mNewName : mNewRemotePath) + ":" +
" " + result.getLogMessage());
return result;
}
}
/**
* Checks if a file with the new name already exists.
*
* @return 'True' if the target path is already used by an existing file.
*/
private boolean targetPathIsUsed(OwnCloudClient client) {
CheckPathExistenceRemoteOperation checkPathExistenceRemoteOperation =
new CheckPathExistenceRemoteOperation(mNewRemotePath, false);
RemoteOperationResult exists = checkPathExistenceRemoteOperation.execute(client);
return exists.isSuccess();
}
}

View File

@ -1,120 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2021 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.common.utils.isOneOf
import timber.log.Timber
import java.io.File
import java.net.URL
import java.util.concurrent.TimeUnit
/**
* Remote operation performing the rename of a remote file or folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
*/
class RenameRemoteFileOperation(
private val oldName: String,
private val oldRemotePath: String,
private val newName: String,
isFolder: Boolean,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() {
private var newRemotePath: String
init {
var parent = (File(oldRemotePath)).parent ?: throw IllegalArgumentException()
if (!parent.endsWith(File.separator)) {
parent = parent.plus(File.separator)
}
newRemotePath = parent.plus(newName)
if (isFolder) {
newRemotePath.plus(File.separator)
}
}
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
var result: RemoteOperationResult<Unit>
try {
if (newName == oldName) {
return RemoteOperationResult<Unit>(ResultCode.OK)
}
if (targetPathIsUsed(client)) {
return RemoteOperationResult<Unit>(ResultCode.INVALID_OVERWRITE)
}
val moveMethod: MoveMethod = MoveMethod(
url = URL((spaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(oldRemotePath)),
destinationUrl = (spaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(newRemotePath),
).apply {
setReadTimeout(RENAME_READ_TIMEOUT, TimeUnit.MILLISECONDS)
setConnectionTimeout(RENAME_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
}
val status = client.executeHttpMethod(moveMethod)
result = if (isSuccess(status)) {
RemoteOperationResult<Unit>(ResultCode.OK)
} else {
RemoteOperationResult<Unit>(moveMethod)
}
Timber.i("Rename $oldRemotePath to $newRemotePath: ${result.logMessage}")
client.exhaustResponse(moveMethod.getResponseBodyAsStream())
return result
} catch (exception: Exception) {
result = RemoteOperationResult<Unit>(exception)
Timber.e(exception, "Rename $oldRemotePath to $newName: ${result.logMessage}")
return result
}
}
/**
* Checks if a file with the new name already exists.
*
* @return 'True' if the target path is already used by an existing file.
*/
private fun targetPathIsUsed(client: OwnCloudClient): Boolean {
val checkPathExistenceRemoteOperation = CheckPathExistenceRemoteOperation(newRemotePath, true)
val exists = checkPathExistenceRemoteOperation.execute(client)
return exists.isSuccess
}
private fun isSuccess(status: Int) = status.isOneOf(HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT)
companion object {
private const val RENAME_READ_TIMEOUT = 10_000L
private const val RENAME_CONNECTION_TIMEOUT = 5_000L
}
}

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
* Copyright (C) 2021 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -24,15 +24,22 @@
package com.owncloud.android.lib.resources.files
import android.content.ContentResolver
import android.net.Uri
import android.provider.OpenableColumns
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.PutMethod
import com.owncloud.android.lib.common.network.ContentUriRequestBody
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.isOneOf
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okio.BufferedSink
import okio.source
import timber.log.Timber
import java.io.IOException
import java.net.URL
class UploadFileFromContentUriOperation(
@ -43,7 +50,7 @@ class UploadFileFromContentUriOperation(
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
val putMethod = PutMethod(URL(client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(uploadPath)), requestBody).apply {
retryOnConnectionFailure = false
setRetryOnConnectionFailure(false)
addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, requestBody.contentLength().toString())
addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, lastModified)
}
@ -63,6 +70,34 @@ class UploadFileFromContentUriOperation(
}
fun isSuccess(status: Int): Boolean {
return status.isOneOf(HttpConstants.HTTP_OK, HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT)
return status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_CREATED || status == HttpConstants.HTTP_NO_CONTENT
}
}
class ContentUriRequestBody(
private val contentResolver: ContentResolver,
private val contentUri: Uri
) : RequestBody() {
override fun contentType(): MediaType? {
val contentType = contentResolver.getType(contentUri) ?: return null
return contentType.toMediaTypeOrNull()
}
override fun contentLength(): Long {
contentResolver.query(contentUri, null, null, null, null)?.use { cursor ->
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
cursor.moveToFirst()
return cursor.getLong(sizeIndex)
} ?: return -1
}
override fun writeTo(sink: BufferedSink) {
val inputStream = contentResolver.openInputStream(contentUri)
?: throw IOException("Couldn't open content URI for reading: $contentUri")
inputStream.source().use { source ->
sink.writeAll(source)
}
}
}

View File

@ -1,148 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.PutMethod
import com.owncloud.android.lib.common.network.FileRequestBody
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.OperationCancelledException
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.common.utils.isOneOf
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import timber.log.Timber
import java.io.File
import java.net.URL
import java.util.concurrent.atomic.AtomicBoolean
/**
* Remote operation performing the upload of a remote file to the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
* @author Abel García de Prada
* @author Juan Carlos Garrote Gascón
*/
open class UploadFileFromFileSystemOperation(
val localPath: String,
val remotePath: String,
val mimeType: String,
val lastModifiedTimestamp: String,
val requiredEtag: String?,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() {
protected val cancellationRequested = AtomicBoolean(false)
protected var putMethod: PutMethod? = null
protected val dataTransferListener: MutableSet<OnDatatransferProgressListener> = HashSet()
protected var fileRequestBody: FileRequestBody? = null
var etag: String = ""
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
var result: RemoteOperationResult<Unit>
try {
if (cancellationRequested.get()) {
// the operation was cancelled before getting it's turn to be executed in the queue of uploads
result = RemoteOperationResult<Unit>(OperationCancelledException())
Timber.i("Upload of $localPath to $remotePath has been cancelled")
} else {
// perform the upload
result = uploadFile(client)
Timber.i("Upload of $localPath to $remotePath: ${result.logMessage}")
}
} catch (e: Exception) {
if (putMethod?.isAborted == true) {
result = RemoteOperationResult<Unit>(OperationCancelledException())
Timber.e(result.exception, "Upload of $localPath to $remotePath has been aborted with this message: ${result.logMessage}")
} else {
result = RemoteOperationResult<Unit>(e)
Timber.e(result.exception, "Upload of $localPath to $remotePath has failed with this message: ${result.logMessage}")
}
}
return result
}
@Throws(Exception::class)
protected open fun uploadFile(client: OwnCloudClient): RemoteOperationResult<Unit> {
val fileToUpload = File(localPath)
val mediaType: MediaType? = mimeType.toMediaTypeOrNull()
fileRequestBody = FileRequestBody(fileToUpload, mediaType).also {
synchronized(dataTransferListener) { it.addDatatransferProgressListeners(dataTransferListener) }
}
val baseStringUrl = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
putMethod = PutMethod(URL(baseStringUrl + WebdavUtils.encodePath(remotePath)), fileRequestBody!!).apply {
retryOnConnectionFailure = false
if (!requiredEtag.isNullOrBlank()) {
addRequestHeader(HttpConstants.IF_MATCH_HEADER, requiredEtag)
}
addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, fileToUpload.length().toString())
addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, lastModifiedTimestamp)
}
val status = client.executeHttpMethod(putMethod)
return if (isSuccess(status)) {
etag = WebdavUtils.getEtagFromResponse(putMethod)
// Get rid of extra quotas
etag = etag.replace("\"", "")
if (etag.isEmpty()) {
Timber.e("Could not read eTag from response uploading %s", localPath)
} else {
Timber.d("File uploaded successfully. New etag for file ${fileToUpload.name} is $etag")
}
RemoteOperationResult<Unit>(ResultCode.OK).apply { data = Unit }
} else { // synchronization failed
RemoteOperationResult<Unit>(putMethod)
}
}
fun addDataTransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListener) { dataTransferListener.add(listener) }
fileRequestBody?.addDatatransferProgressListener(listener)
}
fun removeDataTransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListener) { dataTransferListener.remove(listener) }
fileRequestBody?.removeDatatransferProgressListener(listener)
}
fun cancel() {
synchronized(cancellationRequested) {
cancellationRequested.set(true)
putMethod?.abort()
}
}
fun isSuccess(status: Int): Boolean {
return status.isOneOf(HttpConstants.HTTP_OK, HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT)
}
}

View File

@ -0,0 +1,181 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.PutMethod;
import com.owncloud.android.lib.common.network.FileRequestBody;
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.OperationCancelledException;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import okhttp3.MediaType;
import timber.log.Timber;
import java.io.File;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK;
/**
* Remote operation performing the upload of a remote file to the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
*/
public class UploadRemoteFileOperation extends RemoteOperation {
protected final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
protected String mLocalPath;
protected String mRemotePath;
protected String mMimeType;
protected String mFileLastModifTimestamp;
protected PutMethod mPutMethod = null;
protected String mRequiredEtag = null;
protected Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<>();
protected FileRequestBody mFileRequestBody = null;
public UploadRemoteFileOperation(String localPath, String remotePath, String mimeType,
String fileLastModifTimestamp) {
mLocalPath = localPath;
mRemotePath = remotePath;
mMimeType = mimeType;
mFileLastModifTimestamp = fileLastModifTimestamp;
}
public UploadRemoteFileOperation(String localPath, String remotePath, String mimeType,
String requiredEtag, String fileLastModifTimestamp) {
this(localPath, remotePath, mimeType, fileLastModifTimestamp);
mRequiredEtag = requiredEtag;
}
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result;
try {
if (mCancellationRequested.get()) {
// the operation was cancelled before getting it's turn to be executed in the queue of uploads
result = new RemoteOperationResult<>(new OperationCancelledException());
} else {
// perform the upload
result = uploadFile(client);
Timber.i("Upload of " + mLocalPath + " to " + mRemotePath + ": " + result.getLogMessage());
}
} catch (Exception e) {
if (mPutMethod != null && mPutMethod.isAborted()) {
result = new RemoteOperationResult<>(new OperationCancelledException());
Timber.e(result.getException(),
"Upload of " + mLocalPath + " to " + mRemotePath + ": " + result.getLogMessage());
} else {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Upload of " + mLocalPath + " to " + mRemotePath + ": " + result.getLogMessage());
}
}
return result;
}
protected RemoteOperationResult<?> uploadFile(OwnCloudClient client) throws Exception {
File fileToUpload = new File(mLocalPath);
MediaType mediaType = MediaType.parse(mMimeType);
mFileRequestBody = new FileRequestBody(fileToUpload, mediaType);
synchronized (mDataTransferListeners) {
mFileRequestBody.addDatatransferProgressListeners(mDataTransferListeners);
}
mPutMethod = new PutMethod(
new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mRemotePath)), mFileRequestBody);
mPutMethod.setRetryOnConnectionFailure(false);
if (mRequiredEtag != null && mRequiredEtag.length() > 0) {
mPutMethod.addRequestHeader(HttpConstants.IF_MATCH_HEADER, mRequiredEtag);
}
mPutMethod.addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, String.valueOf(fileToUpload.length()));
mPutMethod.addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, mFileLastModifTimestamp);
int status = client.executeHttpMethod(mPutMethod);
if (isSuccess(status)) {
return new RemoteOperationResult<>(OK);
} else { // synchronization failed
return new RemoteOperationResult<>(mPutMethod);
}
}
public Set<OnDatatransferProgressListener> getDataTransferListeners() {
return mDataTransferListeners;
}
public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.add(listener);
}
if (mFileRequestBody != null) {
mFileRequestBody.addDatatransferProgressListener(listener);
}
}
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.remove(listener);
}
if (mFileRequestBody != null) {
mFileRequestBody.removeDatatransferProgressListener(listener);
}
}
public void cancel() {
synchronized (mCancellationRequested) {
mCancellationRequested.set(true);
if (mPutMethod != null) {
mPutMethod.abort();
}
}
}
public boolean isSuccess(int status) {
return ((status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_CREATED ||
status == HttpConstants.HTTP_NO_CONTENT));
}
}

View File

@ -1,122 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files.chunks
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.methods.webdav.PutMethod
import com.owncloud.android.lib.common.network.ChunkFromFileRequestBody
import com.owncloud.android.lib.common.operations.OperationCancelledException
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.resources.files.FileUtils.MODE_READ_ONLY
import com.owncloud.android.lib.resources.files.UploadFileFromFileSystemOperation
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import timber.log.Timber
import java.io.File
import java.io.RandomAccessFile
import java.net.URL
import java.nio.channels.FileChannel
import java.util.concurrent.TimeUnit
import kotlin.math.ceil
/**
* Remote operation performing the chunked upload of a remote file to the ownCloud server.
*
* @author David A. Velasco
* @author David González Verdugo
* @author Abel García de Prada
*/
class ChunkedUploadFromFileSystemOperation(
private val transferId: String,
localPath: String,
remotePath: String,
mimeType: String,
lastModifiedTimestamp: String,
requiredEtag: String?,
) : UploadFileFromFileSystemOperation(
localPath = localPath,
remotePath = remotePath,
mimeType = mimeType,
lastModifiedTimestamp = lastModifiedTimestamp,
requiredEtag = requiredEtag
) {
@Throws(Exception::class)
override fun uploadFile(client: OwnCloudClient): RemoteOperationResult<Unit> {
lateinit var result: RemoteOperationResult<Unit>
val fileToUpload = File(localPath)
val mediaType: MediaType? = mimeType.toMediaTypeOrNull()
val raf = RandomAccessFile(fileToUpload, MODE_READ_ONLY)
val channel: FileChannel = raf.channel
val fileRequestBody = ChunkFromFileRequestBody(fileToUpload, mediaType, channel).also {
synchronized(dataTransferListener) { it.addDatatransferProgressListeners(dataTransferListener) }
}
val uriPrefix = client.uploadsWebDavUri.toString() + File.separator + transferId
val totalLength = fileToUpload.length()
val chunkCount = ceil(totalLength.toDouble() / CHUNK_SIZE).toLong()
var offset: Long = 0
for (chunkIndex in 0..chunkCount) {
fileRequestBody.setOffset(offset)
if (cancellationRequested.get()) {
result = RemoteOperationResult<Unit>(OperationCancelledException())
break
} else {
putMethod = PutMethod(URL(uriPrefix + File.separator + chunkIndex), fileRequestBody).apply {
if (chunkIndex == chunkCount - 1) {
// Added a high timeout to the last chunk due to when the last chunk
// arrives to the server with the last PUT, all chunks get assembled
// within that PHP request, so last one takes longer.
setReadTimeout(LAST_CHUNK_TIMEOUT.toLong(), TimeUnit.MILLISECONDS)
}
}
val status = client.executeHttpMethod(putMethod)
Timber.d("Upload of $localPath to $remotePath, chunk index $chunkIndex, count $chunkCount, HTTP result status $status")
if (isSuccess(status)) {
result = RemoteOperationResult<Unit>(ResultCode.OK)
} else {
result = RemoteOperationResult<Unit>(putMethod)
break
}
}
offset += CHUNK_SIZE
}
channel.close()
raf.close()
return result
}
companion object {
const val CHUNK_SIZE = 10_240_000L // 10 MB
private const val LAST_CHUNK_TIMEOUT = 900_000 // 15 mins.
}
}

View File

@ -0,0 +1,129 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files.chunks;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.methods.webdav.PutMethod;
import com.owncloud.android.lib.common.network.ChunkFromFileRequestBody;
import com.owncloud.android.lib.common.operations.OperationCancelledException;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation;
import okhttp3.MediaType;
import timber.log.Timber;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.concurrent.TimeUnit;
import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK;
/**
* Remote operation performing the chunked upload of a remote file to the ownCloud server.
*
* @author David A. Velasco
* @author David González Verdugo
*/
public class ChunkedUploadRemoteFileOperation extends UploadRemoteFileOperation {
public static final long CHUNK_SIZE = 1024000;
private static final int LAST_CHUNK_TIMEOUT = 900000; //15 mins.
private String mTransferId;
public ChunkedUploadRemoteFileOperation(String transferId, String localPath, String remotePath, String mimeType,
String requiredEtag, String fileLastModifTimestamp) {
super(localPath, remotePath, mimeType, requiredEtag, fileLastModifTimestamp);
mTransferId = transferId;
}
@Override
protected RemoteOperationResult uploadFile(OwnCloudClient client) throws Exception {
int status;
RemoteOperationResult result = null;
FileChannel channel;
RandomAccessFile raf;
File fileToUpload = new File(mLocalPath);
MediaType mediaType = MediaType.parse(mMimeType);
raf = new RandomAccessFile(fileToUpload, "r");
channel = raf.getChannel();
mFileRequestBody = new ChunkFromFileRequestBody(fileToUpload, mediaType, channel, CHUNK_SIZE);
synchronized (mDataTransferListeners) {
mFileRequestBody.addDatatransferProgressListeners(mDataTransferListeners);
}
long offset = 0;
String uriPrefix = client.getUploadsWebDavUri() + File.separator + mTransferId;
long totalLength = fileToUpload.length();
long chunkCount = (long) Math.ceil((double) totalLength / CHUNK_SIZE);
for (int chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++, offset += CHUNK_SIZE) {
((ChunkFromFileRequestBody) mFileRequestBody).setOffset(offset);
if (mCancellationRequested.get()) {
result = new RemoteOperationResult<>(new OperationCancelledException());
break;
} else {
mPutMethod = new PutMethod(new URL(uriPrefix + File.separator + chunkIndex), mFileRequestBody);
if (chunkIndex == chunkCount - 1) {
// Added a high timeout to the last chunk due to when the last chunk
// arrives to the server with the last PUT, all chunks get assembled
// within that PHP request, so last one takes longer.
mPutMethod.setReadTimeout(LAST_CHUNK_TIMEOUT, TimeUnit.MILLISECONDS);
}
status = client.executeHttpMethod(mPutMethod);
Timber.d("Upload of " + mLocalPath + " to " + mRemotePath +
", chunk index " + chunkIndex + ", count " + chunkCount +
", HTTP result status " + status);
if (isSuccess(status)) {
result = new RemoteOperationResult<>(OK);
} else {
result = new RemoteOperationResult<>(mPutMethod);
break;
}
}
}
if (channel != null) {
channel.close();
}
if (raf != null) {
raf.close();
}
return result;
}
}

View File

@ -22,10 +22,24 @@
*
*/
package com.owncloud.android.lib.common.http
package com.owncloud.android.lib.resources.files.chunks;
import okhttp3.Interceptor
import com.owncloud.android.lib.resources.files.CreateRemoteFolderOperation;
class DummyInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain) = chain.proceed(chain.request())
/**
* Remote operation performing the creation of a new folder to save chunks during an upload to the ownCloud server.
*
* @author David González Verdugo
*/
public class CreateRemoteChunkFolderOperation extends CreateRemoteFolderOperation {
/**
* Constructor
*
* @param remotePath Full path to the new directory to create in the remote server.
* @param createFullPath 'True' means that all the ancestor folders should be created.
*/
public CreateRemoteChunkFolderOperation(String remotePath, boolean createFullPath) {
super(remotePath, createFullPath);
createChunksFolder = true;
}
}

View File

@ -22,8 +22,29 @@
*
*/
package com.owncloud.android.lib.common.http
package com.owncloud.android.lib.resources.files.chunks;
object DebugInterceptorFactory {
fun getInterceptor() = DummyInterceptor()
import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation;
/**
* Remote operation to move the file built from chunks after uploading it
*
* @author David González Verdugo
*/
public class MoveRemoteChunksFileOperation extends MoveRemoteFileOperation {
/**
* Constructor.
*
* @param srcRemotePath Remote path of the file/folder to move.
* @param targetRemotePath Remove path desired for the file/folder after moving it.
* @param overwrite
*/
public MoveRemoteChunksFileOperation(String srcRemotePath, String targetRemotePath, boolean overwrite,
String fileLastModifTimestamp, long fileLength) {
super(srcRemotePath, targetRemotePath, overwrite);
moveChunkedFile = true;
mFileLastModifTimestamp = fileLastModifTimestamp;
mFileLength = fileLength;
}
}

View File

@ -1,58 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2021 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files.chunks
import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod
import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation
/**
* Remote operation to move the file built from chunks after uploading it
*
* @author David González Verdugo
* @author Abel García de Prada
*/
class MoveRemoteChunksFileOperation(
sourceRemotePath: String,
targetRemotePath: String,
private val fileLastModificationTimestamp: String,
private val fileLength: Long
) : MoveRemoteFileOperation(
sourceRemotePath = sourceRemotePath,
targetRemotePath = targetRemotePath,
) {
override fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.uploadsWebDavUri
override fun addRequestHeaders(moveMethod: MoveMethod) {
super.addRequestHeaders(moveMethod)
moveMethod.apply {
addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, fileLastModificationTimestamp)
addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, fileLength.toString())
}
}
}

View File

@ -22,11 +22,20 @@
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files.chunks
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation
package com.owncloud.android.lib.resources.files.chunks;
class RemoveRemoteChunksFolderOperation(remotePath: String) : RemoveRemoteFileOperation(remotePath) {
override fun getSrcWebDavUriForClient(client: OwnCloudClient): String = client.uploadsWebDavUri.toString()
}
import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation;
public class RemoveRemoteChunksFolderOperation extends RemoveRemoteFileOperation {
/**
* Constructor
*
* @param remotePath RemotePath of the remote file or folder to remove from the server
*/
public RemoveRemoteChunksFolderOperation(String remotePath) {
super(remotePath);
removeChunksFolder = true;
}
}

View File

@ -1,41 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2021 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files.services
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.Service
interface ChunkService : Service {
fun removeFile(
remotePath: String
): RemoteOperationResult<Unit>
fun moveFile(
sourceRemotePath: String,
targetRemotePath: String,
fileLastModificationTimestamp: String,
fileLength: Long
): RemoteOperationResult<Unit>
}

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -25,62 +25,7 @@ package com.owncloud.android.lib.resources.files.services
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.Service
import com.owncloud.android.lib.resources.files.RemoteFile
interface FileService : Service {
fun checkPathExistence(
path: String,
isUserLogged: Boolean,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<Boolean>
fun copyFile(
sourceRemotePath: String,
targetRemotePath: String,
sourceSpaceWebDavUrl: String?,
targetSpaceWebDavUrl: String?,
replace: Boolean,
): RemoteOperationResult<String?>
fun createFolder(
remotePath: String,
createFullPath: Boolean,
isChunkFolder: Boolean = false,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<Unit>
fun downloadFile(
remotePath: String,
localTempPath: String
): RemoteOperationResult<Unit>
fun moveFile(
sourceRemotePath: String,
targetRemotePath: String,
spaceWebDavUrl: String?,
replace: Boolean,
): RemoteOperationResult<Unit>
fun readFile(
remotePath: String,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<RemoteFile>
fun refreshFolder(
remotePath: String,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<ArrayList<RemoteFile>>
fun removeFile(
remotePath: String,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<Unit>
fun renameFile(
oldName: String,
oldRemotePath: String,
newName: String,
isFolder: Boolean,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<Unit>
fun checkPathExistence(path: String, isUserLogged: Boolean): RemoteOperationResult<Boolean>
}

View File

@ -1,50 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2021 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files.services.implementation
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.files.chunks.MoveRemoteChunksFileOperation
import com.owncloud.android.lib.resources.files.chunks.RemoveRemoteChunksFolderOperation
import com.owncloud.android.lib.resources.files.services.ChunkService
class OCChunkService(override val client: OwnCloudClient) : ChunkService {
override fun removeFile(remotePath: String): RemoteOperationResult<Unit> =
RemoveRemoteChunksFolderOperation(remotePath = remotePath).execute(client)
override fun moveFile(
sourceRemotePath: String,
targetRemotePath: String,
fileLastModificationTimestamp: String,
fileLength: Long
): RemoteOperationResult<Unit> =
MoveRemoteChunksFileOperation(
sourceRemotePath = sourceRemotePath,
targetRemotePath = targetRemotePath,
fileLastModificationTimestamp = fileLastModificationTimestamp,
fileLength = fileLength,
).execute(client)
}

View File

@ -1,143 +1,34 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
/**
* ownCloud Android client application
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* @author Abel García de Prada
* Copyright (C) 2020 ownCloud GmbH.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.lib.resources.files.services.implementation
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation
import com.owncloud.android.lib.resources.files.CopyRemoteFileOperation
import com.owncloud.android.lib.resources.files.CreateRemoteFolderOperation
import com.owncloud.android.lib.resources.files.DownloadRemoteFileOperation
import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation
import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation
import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation
import com.owncloud.android.lib.resources.files.RemoteFile
import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation
import com.owncloud.android.lib.resources.files.RenameRemoteFileOperation
import com.owncloud.android.lib.resources.files.services.FileService
class OCFileService(override val client: OwnCloudClient) : FileService {
override fun checkPathExistence(
path: String,
isUserLogged: Boolean,
spaceWebDavUrl: String?,
): RemoteOperationResult<Boolean> =
class OCFileService(override val client: OwnCloudClient) :
FileService {
override fun checkPathExistence(path: String, isUserLogged: Boolean): RemoteOperationResult<Boolean> =
CheckPathExistenceRemoteOperation(
remotePath = path,
isUserLoggedIn = isUserLogged,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client)
override fun copyFile(
sourceRemotePath: String,
targetRemotePath: String,
sourceSpaceWebDavUrl: String?,
targetSpaceWebDavUrl: String?,
replace: Boolean,
): RemoteOperationResult<String?> =
CopyRemoteFileOperation(
sourceRemotePath = sourceRemotePath,
targetRemotePath = targetRemotePath,
sourceSpaceWebDavUrl = sourceSpaceWebDavUrl,
targetSpaceWebDavUrl = targetSpaceWebDavUrl,
forceOverride = replace,
).execute(client)
override fun createFolder(
remotePath: String,
createFullPath: Boolean,
isChunkFolder: Boolean,
spaceWebDavUrl: String?,
): RemoteOperationResult<Unit> =
CreateRemoteFolderOperation(
remotePath = remotePath,
createFullPath = createFullPath,
isChunksFolder = isChunkFolder,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client)
override fun downloadFile(
remotePath: String,
localTempPath: String
): RemoteOperationResult<Unit> =
DownloadRemoteFileOperation(
remotePath = remotePath,
localFolderPath = localTempPath
).execute(client)
override fun moveFile(
sourceRemotePath: String,
targetRemotePath: String,
spaceWebDavUrl: String?,
replace: Boolean,
): RemoteOperationResult<Unit> =
MoveRemoteFileOperation(
sourceRemotePath = sourceRemotePath,
targetRemotePath = targetRemotePath,
spaceWebDavUrl = spaceWebDavUrl,
forceOverride = replace,
).execute(client)
override fun readFile(
remotePath: String,
spaceWebDavUrl: String?,
): RemoteOperationResult<RemoteFile> =
ReadRemoteFileOperation(
remotePath = remotePath,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client)
override fun refreshFolder(
remotePath: String,
spaceWebDavUrl: String?,
): RemoteOperationResult<ArrayList<RemoteFile>> =
ReadRemoteFolderOperation(
remotePath = remotePath,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client)
override fun removeFile(
remotePath: String,
spaceWebDavUrl: String?,
): RemoteOperationResult<Unit> =
RemoveRemoteFileOperation(
remotePath = remotePath,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client)
override fun renameFile(
oldName: String,
oldRemotePath: String,
newName: String,
isFolder: Boolean,
spaceWebDavUrl: String?,
): RemoteOperationResult<Unit> =
RenameRemoteFileOperation(
oldName = oldName,
oldRemotePath = oldRemotePath,
newName = newName,
isFolder = isFolder,
spaceWebDavUrl = spaceWebDavUrl,
isUserLogged = isUserLogged
).execute(client)
}

View File

@ -55,7 +55,6 @@ class GetOIDCDiscoveryRemoteOperation : RemoteOperation<OIDCDiscoveryResponse>()
addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE)
}
getMethod.followRedirects = true
val status = client.executeHttpMethod(getMethod)
val responseBody = getMethod.getResponseBodyAsString()

View File

@ -89,6 +89,8 @@ class CreateRemoteShareOperation(
var expirationDateInMillis: Long = INIT_EXPIRATION_DATE_IN_MILLIS // Expiration date to set for the public link
var publicUpload: Boolean = false // Upload permissions for the public link (only folders)
var retrieveShareDetails = false // To retrieve more info about the just created share
private fun buildRequestUri(baseUri: Uri) =
@ -97,7 +99,7 @@ class CreateRemoteShareOperation(
.appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT)
.build()
private fun parseResponse(response: String): ShareResponse {
private fun parseResponse(response: String): ShareResponse? {
val moshi = Moshi.Builder().build()
val commonOcsType: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareItem::class.java)
val adapter: JsonAdapter<CommonOcsResponse<ShareItem>> = moshi.adapter(commonOcsType)
@ -136,6 +138,7 @@ class CreateRemoteShareOperation(
}
private fun createFormBody(): FormBody {
val formBodyBuilder = FormBody.Builder()
.add(PARAM_PATH, remoteFilePath)
.add(PARAM_SHARE_TYPE, shareType.value.toString())
@ -153,10 +156,12 @@ class CreateRemoteShareOperation(
formBodyBuilder.add(PARAM_EXPIRATION_DATE, formattedExpirationDate)
}
if (publicUpload) {
formBodyBuilder.add(PARAM_PUBLIC_UPLOAD, publicUpload.toString())
}
if (password.isNotEmpty()) {
formBodyBuilder.add(PARAM_PASSWORD, password)
}
if (RemoteShare.DEFAULT_PERMISSION != permissions) {
formBodyBuilder.add(PARAM_PERMISSIONS, permissions.toString())
}
@ -203,6 +208,7 @@ class CreateRemoteShareOperation(
private const val PARAM_SHARE_TYPE = "shareType"
private const val PARAM_SHARE_WITH = "shareWith"
private const val PARAM_PASSWORD = "password"
private const val PARAM_PUBLIC_UPLOAD = "publicUpload"
private const val PARAM_PERMISSIONS = "permissions"
//Arguments - constant values

View File

@ -99,11 +99,11 @@ class GetRemoteShareesOperation
.appendQueryParameter(PARAM_PER_PAGE, perPage.toString())
.build()
private fun parseResponse(response: String?): ShareeOcsResponse? {
private fun parseResponse(response: String): ShareeOcsResponse? {
val moshi = Moshi.Builder().build()
val type: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareeOcsResponse::class.java)
val adapter: JsonAdapter<CommonOcsResponse<ShareeOcsResponse>> = moshi.adapter(type)
return response?.let { adapter.fromJson(it)?.ocs?.data }
return adapter.fromJson(response)!!.ocs.data
}
private fun onResultUnsuccessful(
@ -123,7 +123,7 @@ class GetRemoteShareesOperation
private fun onRequestSuccessful(response: String?): RemoteOperationResult<ShareeOcsResponse> {
val result = RemoteOperationResult<ShareeOcsResponse>(OK)
Timber.d("Successful response: $response")
result.data = parseResponse(response)
result.data = parseResponse(response!!)
Timber.d("*** Get Users or groups completed ")
return result
}

View File

@ -36,7 +36,13 @@ import com.owncloud.android.lib.common.http.HttpConstants.VALUE_FORMAT
import com.owncloud.android.lib.common.http.methods.nonwebdav.DeleteMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.CommonOcsResponse
import com.owncloud.android.lib.resources.shares.responses.ShareItem
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import timber.log.Timber
import java.lang.reflect.Type
import java.net.URL
/**
@ -46,10 +52,14 @@ import java.net.URL
* @author David A. Velasco
* @author David González Verdugo
* @author Fernando Sanz Velasco
*/
/**
* Constructor
*
* @param remoteShareId Share ID
*/
class RemoveRemoteShareOperation(private val remoteShareId: String) : RemoteOperation<Unit>() {
class RemoveRemoteShareOperation(private val remoteShareId: String) : RemoteOperation<ShareResponse>() {
private fun buildRequestUri(baseUri: Uri) =
baseUri.buildUpon()
@ -58,12 +68,24 @@ class RemoveRemoteShareOperation(private val remoteShareId: String) : RemoteOper
.appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT)
.build()
private fun parseResponse(response: String): ShareResponse? {
val moshi = Moshi.Builder().build()
val listOfShareItemType: Type = Types.newParameterizedType(List::class.java, ShareItem::class.java)
val commonOcsType: Type = Types.newParameterizedType(CommonOcsResponse::class.java, listOfShareItemType)
val adapter: JsonAdapter<CommonOcsResponse<List<ShareItem>>> = moshi.adapter(commonOcsType)
return adapter.fromJson(response)?.ocs?.data?.let { listOfShareItems ->
ShareResponse(listOfShareItems.map { shareItem ->
shareItem.toRemoteShare()
})
}
}
private fun onResultUnsuccessful(
method: DeleteMethod,
response: String?,
status: Int
): RemoteOperationResult<Unit> {
Timber.e("Failed response while removing share ")
): RemoteOperationResult<ShareResponse> {
Timber.e("Failed response while unshare link ")
if (response != null) {
Timber.e("*** status code: $status; response message: $response")
} else {
@ -72,14 +94,16 @@ class RemoveRemoteShareOperation(private val remoteShareId: String) : RemoteOper
return RemoteOperationResult(method)
}
private fun onRequestSuccessful(response: String?): RemoteOperationResult<Unit> {
val result = RemoteOperationResult<Unit>(RemoteOperationResult.ResultCode.OK)
private fun onRequestSuccessful(response: String?): RemoteOperationResult<ShareResponse> {
val result = RemoteOperationResult<ShareResponse>(RemoteOperationResult.ResultCode.OK)
Timber.d("Successful response: $response")
result.data = parseResponse(response!!)
Timber.d("*** Unshare link completed ")
return result
}
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
override fun run(client: OwnCloudClient): RemoteOperationResult<ShareResponse> {
val requestUri = buildRequestUri(client.baseUri)
val deleteMethod = DeleteMethod(URL(requestUri.toString())).apply {
@ -104,7 +128,7 @@ class RemoveRemoteShareOperation(private val remoteShareId: String) : RemoteOper
private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK
companion object {
// OCS Route
//OCS Route
private const val OCS_ROUTE = "ocs/v2.php/apps/files_sharing/api/v1/shares"
}
}

View File

@ -103,6 +103,13 @@ class UpdateRemoteShareOperation
*/
var permissions: Int = DEFAULT_PERMISSION
/**
* Enable upload permissions to update in Share resource.
*
* Null results in no update applied to the upload permission.
*/
var publicUpload: Boolean? = null
var retrieveShareDetails = false // To retrieve more info about the just updated share
private fun buildRequestUri(baseUri: Uri) =
@ -112,7 +119,7 @@ class UpdateRemoteShareOperation
.appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT)
.build()
private fun parseResponse(response: String): ShareResponse {
private fun parseResponse(response: String): ShareResponse? {
val moshi = Moshi.Builder().build()
val commonOcsType: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareItem::class.java)
val adapter: JsonAdapter<CommonOcsResponse<ShareItem>> = moshi.adapter(commonOcsType)
@ -174,6 +181,10 @@ class UpdateRemoteShareOperation
formBodyBuilder.add(PARAM_EXPIRATION_DATE, formattedExpirationDate)
} // else, ignore - no update
if (publicUpload != null) {
formBodyBuilder.add(PARAM_PUBLIC_UPLOAD, publicUpload.toString())
}
// IMPORTANT: permissions parameter needs to be updated after mPublicUpload parameter,
// otherwise they would be set always as 1 (READ) in the server when mPublicUpload was updated
if (permissions > DEFAULT_PERMISSION) {
@ -184,7 +195,6 @@ class UpdateRemoteShareOperation
return formBodyBuilder
}
override fun run(client: OwnCloudClient): RemoteOperationResult<ShareResponse> {
val requestUri = buildRequestUri(client.baseUri)
@ -195,6 +205,7 @@ class UpdateRemoteShareOperation
addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE)
}
return try {
val status = client.executeHttpMethod(putMethod)
val response = putMethod.getResponseBodyAsString()
@ -222,6 +233,7 @@ class UpdateRemoteShareOperation
private const val PARAM_PASSWORD = "password"
private const val PARAM_EXPIRATION_DATE = "expireDate"
private const val PARAM_PERMISSIONS = "permissions"
private const val PARAM_PUBLIC_UPLOAD = "publicUpload"
//Arguments - constant values
private const val FORMAT_EXPIRATION_DATE = "yyyy-MM-dd"

View File

@ -1,27 +1,21 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
/**
* ownCloud Android client application
*
* @author David González Verdugo
* @author David González Verdugo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* Copyright (C) 2020 ownCloud GmbH.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.lib.resources.shares.services
@ -46,6 +40,7 @@ interface ShareService : Service {
name: String,
password: String,
expirationDate: Long,
publicUpload: Boolean
): RemoteOperationResult<ShareResponse>
fun updateShare(
@ -54,7 +49,8 @@ interface ShareService : Service {
password: String?,
expirationDate: Long,
permissions: Int,
publicUpload: Boolean
): RemoteOperationResult<ShareResponse>
fun deleteShare(remoteId: String): RemoteOperationResult<Unit>
fun deleteShare(remoteId: String): RemoteOperationResult<ShareResponse>
}

View File

@ -1,28 +1,22 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
/**
* ownCloud Android client application
*
* @author Christian Schabesberger
* @author David González Verdugo
* @author Christian Schabesberger
* @author David González Verdugo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* Copyright (C) 2020 ownCloud GmbH.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.lib.resources.shares.services

View File

@ -1,27 +1,21 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
/**
* ownCloud Android client application
*
* @author David González Verdugo
* @author David González Verdugo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* Copyright (C) 2020 ownCloud GmbH.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.lib.resources.shares.services.implementation
@ -36,7 +30,8 @@ import com.owncloud.android.lib.resources.shares.ShareType
import com.owncloud.android.lib.resources.shares.UpdateRemoteShareOperation
import com.owncloud.android.lib.resources.shares.services.ShareService
class OCShareService(override val client: OwnCloudClient) : ShareService {
class OCShareService(override val client: OwnCloudClient) :
ShareService {
override fun getShares(
remoteFilePath: String,
reshares: Boolean,
@ -55,6 +50,7 @@ class OCShareService(override val client: OwnCloudClient) : ShareService {
name: String,
password: String,
expirationDate: Long,
publicUpload: Boolean
): RemoteOperationResult<ShareResponse> =
CreateRemoteShareOperation(
remoteFilePath,
@ -65,6 +61,7 @@ class OCShareService(override val client: OwnCloudClient) : ShareService {
this.name = name
this.password = password
this.expirationDateInMillis = expirationDate
this.publicUpload = publicUpload
this.retrieveShareDetails = true
}.execute(client)
@ -74,6 +71,7 @@ class OCShareService(override val client: OwnCloudClient) : ShareService {
password: String?,
expirationDate: Long,
permissions: Int,
publicUpload: Boolean
): RemoteOperationResult<ShareResponse> =
UpdateRemoteShareOperation(
remoteId
@ -82,10 +80,11 @@ class OCShareService(override val client: OwnCloudClient) : ShareService {
this.password = password
this.expirationDateInMillis = expirationDate
this.permissions = permissions
this.publicUpload = publicUpload
this.retrieveShareDetails = true
}.execute(client)
override fun deleteShare(remoteId: String): RemoteOperationResult<Unit> =
override fun deleteShare(remoteId: String): RemoteOperationResult<ShareResponse> =
RemoveRemoteShareOperation(
remoteId
).execute(client)

View File

@ -1,26 +1,21 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
/**
* ownCloud Android client application
*
* @author David González Verdugo
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* @author David González Verdugo
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* Copyright (C) 2020 ownCloud GmbH.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.lib.resources.shares.services.implementation

View File

@ -1,99 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.resources.spaces
import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.spaces.responses.SpaceResponse
import com.owncloud.android.lib.resources.spaces.responses.SpacesResponseWrapper
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import timber.log.Timber
import java.net.URL
class GetRemoteSpacesOperation : RemoteOperation<List<SpaceResponse>>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<List<SpaceResponse>> {
val requestUri = buildRequestUri(client.baseUri)
val getMethod = GetMethod(URL(requestUri.toString()))
return try {
val status = client.executeHttpMethod(getMethod)
val response = getMethod.getResponseBodyAsString()
if (isSuccess(status)) {
onRequestSuccessful(response)
} else {
onResultUnsuccessful(getMethod, response, status)
}
} catch (e: Exception) {
Timber.e(e, "Exception while getting remote shares")
RemoteOperationResult(e)
}
}
private fun buildRequestUri(baseUri: Uri) =
baseUri.buildUpon()
.appendEncodedPath(GRAPH_API_PATH)
.appendEncodedPath(ENDPOINT_SPACES_LIST)
.build()
private fun parseResponse(response: String): List<SpaceResponse> {
val moshi = Moshi.Builder().build()
val adapter: JsonAdapter<SpacesResponseWrapper> = moshi.adapter(SpacesResponseWrapper::class.java)
return adapter.fromJson(response)?.value ?: listOf()
}
private fun onResultUnsuccessful(
method: GetMethod,
response: String?,
status: Int
): RemoteOperationResult<List<SpaceResponse>> {
Timber.e("Failed response while getting spaces for user")
if (response != null) {
Timber.e("*** status code: $status; response message: $response")
} else {
Timber.e("*** status code: $status")
}
return RemoteOperationResult(method)
}
private fun onRequestSuccessful(response: String?): RemoteOperationResult<List<SpaceResponse>> {
val result = RemoteOperationResult<List<SpaceResponse>>(RemoteOperationResult.ResultCode.OK)
Timber.d("Successful response: $response")
result.data = response?.let { parseResponse(it) } ?: listOf()
Timber.d("*** Fetch of spaces completed and parsed to ${result.data}")
return result
}
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK
companion object {
private const val GRAPH_API_PATH = "graph/v1.0"
private const val ENDPOINT_SPACES_LIST = "me/drives"
}
}

View File

@ -1,98 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.resources.spaces.responses
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SpacesResponseWrapper(
val value: List<SpaceResponse>
)
@JsonClass(generateAdapter = true)
data class SpaceResponse(
val description: String?,
val driveAlias: String,
val driveType: String,
val id: String,
val lastModifiedDateTime: String?,
val name: String,
val owner: OwnerResponse?,
val quota: QuotaResponse?,
val root: RootResponse,
val special: List<SpecialResponse>?,
val webUrl: String,
)
@JsonClass(generateAdapter = true)
data class OwnerResponse(
val user: UserResponse
)
@JsonClass(generateAdapter = true)
data class QuotaResponse(
val remaining: Long?,
val state: String?,
val total: Long,
val used: Long?,
)
@JsonClass(generateAdapter = true)
data class RootResponse(
val eTag: String?,
val id: String,
val webDavUrl: String,
val deleted: DeleteResponse?,
)
@JsonClass(generateAdapter = true)
data class SpecialResponse(
val eTag: String,
val file: FileResponse,
val id: String,
val lastModifiedDateTime: String,
val name: String,
val size: Int,
val specialFolder: SpecialFolderResponse,
val webDavUrl: String
)
@JsonClass(generateAdapter = true)
data class UserResponse(
val id: String
)
@JsonClass(generateAdapter = true)
data class FileResponse(
val mimeType: String
)
@JsonClass(generateAdapter = true)
data class DeleteResponse(
val state: String,
)
@JsonClass(generateAdapter = true)
data class SpecialFolderResponse(
val name: String
)

View File

@ -1,31 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.resources.spaces.services
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.Service
import com.owncloud.android.lib.resources.spaces.responses.SpaceResponse
interface SpacesService : Service {
fun getSpaces(): RemoteOperationResult<List<SpaceResponse>>
}

View File

@ -45,7 +45,7 @@ import java.net.URL
/**
* Get the Capabilities from the server
* Save Result.getData in a RemoteCapability object
* Save in Result.getData in a RemoteCapability object
*
* @author masensio
* @author David González Verdugo

View File

@ -30,7 +30,9 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.resources.status.HttpScheme.HTTPS_PREFIX
import com.owncloud.android.lib.resources.status.HttpScheme.HTTP_PREFIX
import com.owncloud.android.lib.resources.status.HttpScheme.HTTP_SCHEME
import org.json.JSONException
import timber.log.Timber
/**
* Checks if the server is valid
@ -43,25 +45,27 @@ import org.json.JSONException
class GetRemoteStatusOperation : RemoteOperation<RemoteServerInfo>() {
public override fun run(client: OwnCloudClient): RemoteOperationResult<RemoteServerInfo> {
if (!usesHttpOrHttps(client.baseUri)) {
client.baseUri = buildFullHttpsUrl(client.baseUri)
client.baseUri = buildFullHttpsUrl(client.baseUri)
var result = tryToConnect(client)
if (!(result.code == ResultCode.OK || result.code == ResultCode.OK_SSL) && !result.isSslRecoverableException) {
Timber.d("Establishing secure connection failed, trying non secure connection")
client.baseUri = client.baseUri.buildUpon().scheme(HTTP_SCHEME).build()
result = tryToConnect(client)
}
return tryToConnect(client)
}
private fun updateClientBaseUrl(client: OwnCloudClient, newBaseUrl: String) {
client.baseUri = Uri.parse(newBaseUrl)
return result
}
private fun tryToConnect(client: OwnCloudClient): RemoteOperationResult<RemoteServerInfo> {
val baseUrl = client.baseUri.toString()
client.setFollowRedirects(false)
return try {
val requester = StatusRequester()
val requestResult = requester.request(baseUrl, client)
val result = requester.handleRequestResult(requestResult, baseUrl)
updateClientBaseUrl(client, result.data.baseUrl)
return result
val requestResult = requester.requestAndFollowRedirects(baseUrl, client)
requester.handleRequestResult(requestResult, baseUrl).also {
client.baseUri = Uri.parse(it.data.baseUrl)
}
} catch (e: JSONException) {
RemoteOperationResult(ResultCode.INSTANCE_NOT_CONFIGURED)
} catch (e: Exception) {

View File

@ -1,33 +1,29 @@
/**
* ownCloud Android Library is available under MIT license
/* ownCloud Android Library is available under MIT license
* @author masensio
* @author David González Verdugo
* @author Abel García de Prada
* Copyright (C) 2020 ownCloud GmbH.
*
* @author masensio
* @author David González Verdugo
* @author Abel García de Prada
* @author Juan Carlos Garrote Gascón
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* Copyright (C) 2022 ownCloud GmbH.
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.resources.status
/**
@ -37,7 +33,7 @@ data class RemoteCapability(
var accountName: String = "",
// Server version
var versionMajor: Int = 0,
var versionMayor: Int = 0,
var versionMinor: Int = 0,
var versionMicro: Int = 0,
var versionString: String = "",
@ -70,12 +66,7 @@ data class RemoteCapability(
// Files
var filesBigFileChunking: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN,
var filesUndelete: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN,
var filesVersioning: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN,
val filesPrivateLinks: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN,
val filesAppProviders: List<RemoteAppProviders>?,
// Spaces
val spaces: RemoteSpaces?,
var filesVersioning: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN
) {
/**
* Enum for Boolean Type in capabilities, with values:
@ -107,19 +98,4 @@ data class RemoteCapability(
}
}
}
data class RemoteAppProviders(
val enabled: Boolean,
val version: String,
val appsUrl: String?,
val openUrl: String?,
val openWebUrl: String?,
val newUrl: String?,
)
data class RemoteSpaces(
val enabled: Boolean,
val projects: Boolean,
val shareJail: Boolean,
)
}

View File

@ -47,7 +47,7 @@ internal class StatusRequester {
redirectedUrl: String
) = redirectedToNonSecureLocationBefore ||
(baseUrl.startsWith(HTTPS_SCHEME) &&
!redirectedUrl.startsWith(HTTPS_SCHEME))
!redirectedUrl.startsWith(HTTPS_SCHEME))
fun updateLocationWithRedirectPath(oldLocation: String, redirectedLocation: String): String {
/** Redirection with different endpoint.
@ -73,18 +73,36 @@ internal class StatusRequester {
data class RequestResult(
val getMethod: GetMethod,
val status: Int,
val redirectedToUnsecureLocation: Boolean,
val lastLocation: String
)
fun request(baseLocation: String, client: OwnCloudClient): RequestResult {
val currentLocation = baseLocation + OwnCloudClient.STATUS_PATH
fun requestAndFollowRedirects(baseLocation: String, client: OwnCloudClient): RequestResult {
var currentLocation = baseLocation + OwnCloudClient.STATUS_PATH
var redirectedToUnsecureLocation = false
var status: Int
val getMethod = getGetMethod(currentLocation)
getMethod.followPermanentRedirects = true
status = client.executeHttpMethod(getMethod)
while (true) {
val getMethod = getGetMethod(currentLocation)
return RequestResult(getMethod, status, getMethod.getFinalUrl().toString())
status = client.executeHttpMethod(getMethod)
val result =
if (status.isSuccess()) RemoteOperationResult<OwnCloudVersion>(RemoteOperationResult.ResultCode.OK)
else RemoteOperationResult(getMethod)
if (result.redirectedLocation.isNullOrEmpty() || result.isSuccess) {
return RequestResult(getMethod, status, redirectedToUnsecureLocation, currentLocation)
} else {
val nextLocation = updateLocationWithRedirectPath(currentLocation, result.redirectedLocation)
redirectedToUnsecureLocation =
isRedirectedToNonSecureConnection(
redirectedToUnsecureLocation,
currentLocation,
nextLocation
)
currentLocation = nextLocation
}
}
}
private fun Int.isSuccess() = this == HttpConstants.HTTP_OK
@ -104,8 +122,12 @@ internal class StatusRequester {
// the version object will be returned even if the version is invalid, no error code;
// every app will decide how to act if (ocVersion.isVersionValid() == false)
val result: RemoteOperationResult<RemoteServerInfo> =
if (baseUrl.startsWith(HTTPS_SCHEME)) RemoteOperationResult(RemoteOperationResult.ResultCode.OK_SSL)
else RemoteOperationResult(RemoteOperationResult.ResultCode.OK_NO_SSL)
if (requestResult.redirectedToUnsecureLocation) {
RemoteOperationResult(RemoteOperationResult.ResultCode.OK_REDIRECT_TO_NON_SECURE_CONNECTION)
} else {
if (baseUrl.startsWith(HTTPS_SCHEME)) RemoteOperationResult(RemoteOperationResult.ResultCode.OK_SSL)
else RemoteOperationResult(RemoteOperationResult.ResultCode.OK_NO_SSL)
}
val finalUrl = URL(requestResult.lastLocation)
val finalBaseUrl = URL(
finalUrl.protocol,

View File

@ -1,35 +1,31 @@
/**
* ownCloud Android Library is available under MIT license
/* ownCloud Android Library is available under MIT license
* @author Abel García de Prada
* Copyright (C) 2020 ownCloud GmbH.
*
* @author Abel García de Prada
* @author Juan Carlos Garrote Gascón
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* Copyright (C) 2022 ownCloud GmbH.
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.resources.status.responses
import com.owncloud.android.lib.resources.status.RemoteCapability
import com.owncloud.android.lib.resources.status.RemoteCapability.*
import com.owncloud.android.lib.resources.status.RemoteCapability.CapabilityBooleanType
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@ -40,7 +36,7 @@ data class CapabilityResponse(
val capabilities: Capabilities?
) {
fun toRemoteCapability(): RemoteCapability = RemoteCapability(
versionMajor = serverVersion?.major ?: 0,
versionMayor = serverVersion?.major ?: 0,
versionMinor = serverVersion?.minor ?: 0,
versionMicro = serverVersion?.micro ?: 0,
versionString = serverVersion?.string ?: "",
@ -72,12 +68,9 @@ data class CapabilityResponse(
filesBigFileChunking = CapabilityBooleanType.fromBooleanValue(capabilities?.fileCapabilities?.bigfilechunking),
filesUndelete = CapabilityBooleanType.fromBooleanValue(capabilities?.fileCapabilities?.undelete),
filesVersioning = CapabilityBooleanType.fromBooleanValue(capabilities?.fileCapabilities?.versioning),
filesPrivateLinks = capabilities?.fileCapabilities?.privateLinks?.let { CapabilityBooleanType.fromBooleanValue(it) } ?: CapabilityBooleanType.UNKNOWN,
filesAppProviders = capabilities?.fileCapabilities?.appProviders?.map { it.toAppProviders() },
filesSharingFederationIncoming = CapabilityBooleanType.fromBooleanValue(capabilities?.fileSharingCapabilities?.fileSharingFederation?.incoming),
filesSharingFederationOutgoing = CapabilityBooleanType.fromBooleanValue(capabilities?.fileSharingCapabilities?.fileSharingFederation?.outgoing),
filesSharingUserProfilePicture = CapabilityBooleanType.fromBooleanValue(capabilities?.fileSharingCapabilities?.fileSharingUser?.profilePicture),
spaces = capabilities?.spacesCapabilities?.toSpaces(),
)
}
@ -90,9 +83,7 @@ data class Capabilities(
@Json(name = "files")
val fileCapabilities: FileCapabilities?,
@Json(name = "dav")
val davCapabilities: DavCapabilities?,
@Json(name = "spaces")
val spacesCapabilities: SpacesCapabilities?,
val davCapabilities: DavCapabilities?
)
@JsonClass(generateAdapter = true)
@ -169,43 +160,14 @@ data class FileSharingUser(
data class FileCapabilities(
val bigfilechunking: Boolean?,
val undelete: Boolean?,
val versioning: Boolean?,
val privateLinks: Boolean?,
@Json(name = "app_providers")
val appProviders: List<AppProvider>?
val versioning: Boolean?
)
@JsonClass(generateAdapter = true)
data class AppProvider(
val enabled: Boolean,
val version: String,
@Json(name = "apps_url")
val appsUrl: String?,
@Json(name = "open_url")
val openUrl: String?,
@Json(name = "open_web_url")
val openWebUrl: String?,
@Json(name = "new_url")
val newUrl: String?,
) {
fun toAppProviders() = RemoteAppProviders(enabled, version, appsUrl, openUrl, openWebUrl, newUrl)
}
@JsonClass(generateAdapter = true)
data class DavCapabilities(
val chunking: String?
)
@JsonClass(generateAdapter = true)
data class SpacesCapabilities(
val enabled: Boolean,
val projects: Boolean,
@Json(name = "share_jail")
val shareJail: Boolean,
) {
fun toSpaces() = RemoteSpaces(enabled, projects, shareJail)
}
@JsonClass(generateAdapter = true)
data class ServerVersion(
var major: Int?,

View File

@ -1,27 +1,21 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
/**
* ownCloud Android client application
*
* @author David González Verdugo
* @author David González Verdugo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* Copyright (C) 2020 ownCloud GmbH.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.lib.resources.status.services

Some files were not shown because too many files have changed in this diff Show More