diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c60fad306dd258e5849567c4b98479ee6a5e8dcf..cdb53b009103ca116c19ba4611ba2048f05f6dda 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,20 +1,18 @@ image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:latest" stages: -- test -- build + - test + - build before_script: -- git submodule sync -- git submodule update --init --recursive -- export GRADLE_USER_HOME=$(pwd)/.gradle -- chmod +x ./gradlew + - export GRADLE_USER_HOME=$(pwd)/.gradle + - chmod +x ./gradlew cache: key: ${CI_PROJECT_ID} paths: - - .gradle/ + - .gradle/ test: @@ -35,9 +33,9 @@ test: build: stage: build script: - - ./gradlew assemble + - ./gradlew assemble artifacts: paths: - - app/build/outputs/apk/ + - app/build/outputs/apk/ expire_in: 4 weeks diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 34e74166112d7eca22fde53420986cd1f42474bb..0000000000000000000000000000000000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "nextcloud-android-lib"] - path = nextcloud-android-lib - url = ../nextcloud-android-lib.git \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 0bda024227adbb6b814b990cd47dad42e43e78be..35eb1ddfbbc029bcab630581847471d7f238ec53 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 12df8d963a55c16b57401d8f69f1acbb2f1f8272..bbcd73a2dbd9bc491415c03228ca914ccfa080ef 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,16 +1,19 @@ +plugins { + id 'com.android.application' +} + -import java.text.SimpleDateFormat; -apply plugin: 'com.android.application' def versionMajor = 1 -def versionMinor = 0 -def versionPatch = 1 +def versionMinor = 1 +def versionPatch = 0 + def getTestProp(String propName) { def result = "" - if(project.hasProperty(propName)){ + if (project.hasProperty(propName)) { result =project.property(propName).toString() - }else if(project.rootProject.file('local.properties').exists()){ + } else if(project.rootProject.file('local.properties').exists()) { Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newReader()) result = properties.getProperty(propName) @@ -20,13 +23,15 @@ def getTestProp(String propName) { android { - compileSdkVersion 31 + compileSdk 31 defaultConfig { applicationId "foundation.e.drive" - minSdkVersion 26 + minSdk 26 + targetSdk 31 versionCode versionMajor * 1000000 + versionMinor * 1000 + versionPatch versionName "${versionMajor}.${versionMinor}.${versionPatch}" setProperty("archivesBaseName", "eDrive-$versionName") + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { @@ -35,10 +40,6 @@ android { } } - lintOptions { - abortOnError false - } - testOptions { unitTests { @@ -55,37 +56,38 @@ android { viewBinding true } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } } dependencies { - api project(':NextcloudLib') + implementation 'com.github.nextcloud:android-library:2.10.1' + implementation "commons-httpclient:commons-httpclient:3.1@jar" implementation fileTree(include: ['*.jar'], dir: 'libs') api 'androidx.annotation:annotation:1.3.0' - implementation 'androidx.core:core:1.6.0' - implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.core:core:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.1' implementation "androidx.constraintlayout:constraintlayout:2.1.3" - implementation 'com.google.android.material:material:1.5.0' + implementation 'com.google.android.material:material:1.6.0' implementation 'com.github.bumptech.glide:glide:4.13.1' - - def work_version = "2.7.1" - // (Java only) - implementation "androidx.work:work-runtime:$work_version" + implementation "androidx.work:work-runtime:2.7.1" + implementation 'androidx.test:core:1.4.0' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.annotation:annotation:1.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - androidTestImplementation 'junit:junit:4.12' + androidTestImplementation 'junit:junit:4.13.2' testImplementation 'androidx.test:runner:1.4.0' testImplementation 'androidx.test:rules:1.4.0' - testImplementation 'junit:junit:4.12' - testImplementation 'org.robolectric:robolectric:4.8.1' - testImplementation 'org.mockito:mockito-inline:3.4.0' - testImplementation "androidx.work:work-testing:$work_version" + + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.robolectric:robolectric:4.4' + testImplementation 'org.mockito:mockito-core:3.4.0' + //testImplementation 'org.mockito:mockito-inline:3.4.0' + testImplementation 'androidx.work:work-testing:2.7.1' } diff --git a/app/src/androidTest/java/foundation/e/drive/services/ObserverServiceInstrumentedTest.java b/app/src/androidTest/java/foundation/e/drive/services/ObserverServiceInstrumentedTest.java deleted file mode 100644 index bd5fe22cd23116c10c5b0b339b4a18eb8ba736dc..0000000000000000000000000000000000000000 --- a/app/src/androidTest/java/foundation/e/drive/services/ObserverServiceInstrumentedTest.java +++ /dev/null @@ -1,100 +0,0 @@ -package foundation.e.drive.services; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Environment; -import android.support.test.InstrumentationRegistry; -import android.support.test.rule.ServiceTestRule; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.concurrent.TimeoutException; - -import static android.support.test.InstrumentationRegistry.getContext; -import static org.junit.Assert.*; - - - - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ObserverServiceInstrumentedTest { - private final static String NEW_SMALL_FILE_PATH = Environment.getExternalStorageDirectory() + File.separator + Environment.DIRECTORY_DOCUMENTS+File.separator+"test-small-file.txt"; - private final static String ACCOUNT_NAME= ""; - private final static String ACCOUNT_PASS=""; - private final static String ACCOUNT_TYPE=""; - - - @Rule - public final ServiceTestRule mServiceRule = new ServiceTestRule(); - - - @Before - public void RegisterAccount() throws Exception { - final Account account = new Account(ACCOUNT_NAME, ACCOUNT_TYPE); - AccountManager.get(getContext()).addAccountExplicitly(account, ACCOUNT_PASS, null); - - } - - - /** - * Souldn't it be more for a unit test ? - * @throws IOException - */ - @Before - public void createLocalSmallFile() throws IOException { - - File file = new File(NEW_SMALL_FILE_PATH); - file.createNewFile(); - String content = "this a very small content"; -//write the bytes in file - if(file.exists()) - { - OutputStream fo = new FileOutputStream(file); - fo.write(content.getBytes()); - fo.close(); - System.out.println("file created: "+file); - } - } - - - @After - public void deleteLocalSmallFile(){ - File file = new File(NEW_SMALL_FILE_PATH); - //deleting the file - file.delete(); - } - - - @Test - public void testWithStartedService() throws TimeoutException { - mServiceRule.startService( - new Intent(InstrumentationRegistry.getTargetContext(), ObserverService.class)); - //do something - } - - @Test - public void useAppContext() throws Exception { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - - assertEquals("foundation.e.drive", appContext.getPackageName()); - } -} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dc5d04a4bcefec9387e03ddc06f33a33cb681765..4b674380f3f5881c8de08b6fb4b42f9827ae901b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,17 +1,21 @@ + - + + android:roundIcon="@mipmap/ic_eelo_round" + android:requestLegacyExternalStorage="true"> + android:exported="true" + android:label="@string/app_widget_description"> @@ -92,14 +97,16 @@ + android:enabled="true" + android:exported="true"> + android:enabled="true" + android:exported="true"> diff --git a/app/src/main/java/foundation/e/drive/activity/AccountsActivity.java b/app/src/main/java/foundation/e/drive/activity/AccountsActivity.java index f40413ca15dfecd4bee2b826a77ccacfa80b8817..109936ae769337ef37abbda1013e8bee2b3985e9 100644 --- a/app/src/main/java/foundation/e/drive/activity/AccountsActivity.java +++ b/app/src/main/java/foundation/e/drive/activity/AccountsActivity.java @@ -36,6 +36,7 @@ import com.owncloud.android.lib.common.OwnCloudClient; import foundation.e.drive.R; import foundation.e.drive.databinding.ActivityAccountsBinding; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DavClientProvider; import foundation.e.drive.widgets.EDriveWidget; public class AccountsActivity extends AppCompatActivity { @@ -70,7 +71,7 @@ public class AccountsActivity extends AppCompatActivity { final AccountManager accountManager = AccountManager.get(this); final Account account = CommonUtils.getAccount(getString(R.string.eelo_account_type), accountManager); - final OwnCloudClient client = CommonUtils.getOwnCloudClient(account, this); + final OwnCloudClient client = DavClientProvider.getInstance().getClientInstance(account, this); final String usedQuota = accountManager.getUserData(account, ACCOUNT_DATA_USED_QUOTA_KEY); final String totalQuota = accountManager.getUserData(account, ACCOUNT_DATA_TOTAL_QUOTA_KEY); diff --git a/app/src/main/java/foundation/e/drive/models/SyncWrapper.java b/app/src/main/java/foundation/e/drive/models/SyncWrapper.java index 598f518246129a9f5175288ee3f25b88c6a90efd..f6bc93133ed9ee74bcd0d5de499b9d4ae60c676b 100644 --- a/app/src/main/java/foundation/e/drive/models/SyncWrapper.java +++ b/app/src/main/java/foundation/e/drive/models/SyncWrapper.java @@ -7,6 +7,7 @@ */ package foundation.e.drive.models; +import android.accounts.Account; import android.content.Context; import com.owncloud.android.lib.common.operations.RemoteOperation; @@ -29,9 +30,9 @@ public class SyncWrapper { * @param request SyncRequest at origin of the file transfer * @param context Application context, used to create RemoteOperation to run */ - public SyncWrapper(SyncRequest request, Context context) { + public SyncWrapper(final SyncRequest request, final Account account, final Context context) { this.request = request; - remoteOperation = createRemoteOperation(request, context); + remoteOperation = createRemoteOperation(request, account, context); isRunning = true; } @@ -53,12 +54,12 @@ public class SyncWrapper { * @param context App context to be passed to RemoteOperation's contructor * @return RemoteOperation for Upload/Download request or null */ - private static RemoteOperation createRemoteOperation(SyncRequest request, Context context) { + private static RemoteOperation createRemoteOperation(final SyncRequest request, final Account account, final Context context) { final RemoteOperation operation; switch (request.getOperationType()) { case UPLOAD: final SyncedFileState sfs = request.getSyncedFileState(); - operation = new UploadFileOperation(sfs, context); + operation = new UploadFileOperation(sfs, account, context); break; case DOWNLOAD: final DownloadRequest downloadRequest = (DownloadRequest) request; diff --git a/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java b/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java index 5d8892d30495ef2a1abca1b61c5d68f41be600ab..cc4115ee99c173b676222c190353159dae129f99 100644 --- a/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java @@ -14,6 +14,7 @@ import android.util.Log; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.resources.files.DownloadFileRemoteOperation; import com.owncloud.android.lib.resources.files.FileUtils; import com.owncloud.android.lib.resources.files.model.RemoteFile; import java.io.File; @@ -24,16 +25,17 @@ import foundation.e.drive.utils.CommonUtils; /** * @author Vincent Bourgmayer * Encapsulate a global download process for a file + * /!\ Doesn't require NextcloudClient yet */ public class DownloadFileOperation extends RemoteOperation { private final static String TAG = DownloadFileOperation.class.getSimpleName(); private final RemoteFile remoteFile; - private Context context; - private String targetPath; + private final Context context; + private final String targetPath; private int restartCounter =0; - private SyncedFileState syncedFileState; - private String previousEtag; + private final SyncedFileState syncedFileState; + private final String previousEtag; /** * COnstructor of download operation where syncedFileState is already known @@ -50,76 +52,68 @@ public class DownloadFileOperation extends RemoteOperation { @Override protected RemoteOperationResult run(OwnCloudClient ownCloudClient) { - Log.i(TAG, "run(ownCloudClient)"); - - //get or build synced file equivalent of this.mFile - if (syncedFileState == null || targetPath == null || targetPath.isEmpty()) { - Log.e(TAG, "syncedFileState or targetPath is empty or null. Can't Download in those conditions"); - return new RemoteOperationResult(RemoteOperationResult.ResultCode.FORBIDDEN); - } else if (syncedFileState.getId() == -1) { + Log.i(TAG, "run(client) for "+remoteFile.getRemotePath()); + if (syncedFileState.getId() == -1) { this.syncedFileState.setId(DbHelper.manageSyncedFileStateDB(this.syncedFileState, "INSERT", context)); } - if (syncedFileState.getLastETAG().equals(remoteFile.getEtag()) && syncedFileState.getLocalLastModified() > 0L) { - //Same etag and localLastModified not null mean the file is up to date + if (syncedFileState.getLastETAG().equals(remoteFile.getEtag()) + && syncedFileState.getLocalLastModified() > 0L) { + //file is up to date Log.w(TAG, "File already up-to-date"); return new RemoteOperationResult(RemoteOperationResult.ResultCode.ETAG_UNCHANGED); } - - final String tmpTargetPath = context.getExternalCacheDir()+ FileUtils.PATH_SEPARATOR+ syncedFileState.getName(); + + final String tmpTargetFolderPath = context.getExternalCacheDir().getAbsolutePath(); final DownloadFileRemoteOperation downloadOperation = new DownloadFileRemoteOperation(remoteFile.getRemotePath(), - tmpTargetPath); + tmpTargetFolderPath); final RemoteOperationResult downloadResult = downloadOperation.execute(ownCloudClient); RemoteOperationResult.ResultCode resultCode; boolean mustRestart = true; + if (downloadResult.isSuccess()) { - final File tmpLocalFile = new File(tmpTargetPath); - if (!tmpLocalFile.exists()) { + final String tmpFilePath = tmpTargetFolderPath+remoteFile.getRemotePath(); + final File tmpFile = new File(tmpFilePath); + + if (!tmpFile.exists()) { Log.e(TAG, "Downloaded file doesn't exist or is null"); resultCode = RemoteOperationResult.ResultCode.FILE_NOT_FOUND; - } else if (tmpLocalFile.length() != remoteFile.getLength()) { - - Log.e(TAG, "Local and remote file doesn't have the same size."); + } else if (tmpFile.length() != remoteFile.getLength()) { + Log.e(TAG, "Local and remote file doesn't have the same size"); resultCode = RemoteOperationResult.ResultCode.INVALID_OVERWRITE; - tmpLocalFile.delete(); + tmpFile.delete(); + } else { //file has been correctly download. - } else { - //file has been correctly download. final File localFile = new File(targetPath); - if (localFile.exists()) { + if (!localFile.getParentFile().exists()) { + localFile.getParentFile().mkdirs(); + } else if (localFile.exists()) { localFile.delete(); } - //Check parentFolder existence and create if needed - final String parentFoldersPath = localFile.getParent(); - final File localParentFile = new File(parentFoldersPath); - if (!localParentFile.exists()) { - if (localParentFile.mkdirs()) - Log.d(TAG, "Created folders: "+parentFoldersPath); - else - Log.d(TAG, "Can't create folders: "+parentFoldersPath); - } - boolean renameResult = tmpLocalFile.renameTo(localFile); - if (!renameResult) - Log.d(TAG, "File hasn't been successfully moved at its place"); + if (!tmpFile.renameTo(localFile)) { + Log.d(TAG, "failed to move " + tmpFile.getAbsolutePath() + " to " + targetPath); + return new RemoteOperationResult(RemoteOperationResult.ResultCode.FORBIDDEN); + } + Log.d(TAG, "File moved to: "+localFile.getAbsolutePath()); syncedFileState.setLocalLastModified(localFile.lastModified()) .setLastETAG(remoteFile.getEtag()); + mustRestart = false; resultCode = RemoteOperationResult.ResultCode.OK; //needed to make Gallery show new image CommonUtils.doActionMediaScannerConnexionScanFile(context, syncedFileState.getLocalPath()); } - } else { - //If download failed + } else { //If download failed Log.e(TAG, "Download failed: "+downloadResult.getLogMessage()); resultCode = RemoteOperationResult.ResultCode.UNKNOWN_ERROR; } if (mustRestart) { - Log.w(TAG, restartCounter+" unsuccessfull trial.s of downloading file " + Log.w(TAG, restartCounter+" unsuccessfull trial.s of downloading" + remoteFile.getRemotePath()); syncedFileState.setLastETAG(this.previousEtag); if (this.restartCounter < 3) { diff --git a/app/src/main/java/foundation/e/drive/operations/DownloadFileRemoteOperation.java b/app/src/main/java/foundation/e/drive/operations/DownloadFileRemoteOperation.java deleted file mode 100644 index 107b6ff1862fee9d400e6a5780c9ce14fa0464b0..0000000000000000000000000000000000000000 --- a/app/src/main/java/foundation/e/drive/operations/DownloadFileRemoteOperation.java +++ /dev/null @@ -1,188 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2015 ownCloud Inc. - * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2022. - * 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 foundation.e.drive.operations; - -import android.util.Log; - -import com.owncloud.android.lib.common.OwnCloudClient; -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.utils.Log_OC; -import org.apache.commons.httpclient.Header; -import org.apache.commons.httpclient.HttpStatus; -import org.apache.commons.httpclient.methods.GetMethod; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Date; -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 - * @author Vincent Bourgmayer - */ -class DownloadFileRemoteOperation extends RemoteOperation { - private static final String TAG = DownloadFileRemoteOperation.class.getSimpleName(); - - private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); - private long mModificationTimestamp = 0; - private String mEtag = ""; - private GetMethod mGet; - private final String mRemotePath; - private final String mLocalFolderPath; - - /** - * Constructor - * @param remotePath Path of file on the server - * @param localFolderPath Path of file on the device - */ - public DownloadFileRemoteOperation(String remotePath, String localFolderPath) { - mRemotePath = remotePath; - mLocalFolderPath = localFolderPath; - } - - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - RemoteOperationResult result = null; - /// download will be performed to a temporal file, then moved to the final location - File tmpFile = new File(getTmpPath()); - /// perform the download - try { - File parentFile = tmpFile.getParentFile(); - if (parentFile == null) { - Log.e(TAG, "getParentFile() returned null. Returning to prevent a NPE"); - return new RemoteOperationResult(RemoteOperationResult.ResultCode.UNKNOWN_ERROR); - } - - parentFile.mkdirs(); - int status = downloadFile(client, tmpFile); - result = new RemoteOperationResult(isSuccess(status), mGet); - Log_OC.i(TAG, "Download of " + mRemotePath + " to " + getTmpPath() + ": " + - result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Download of " + mRemotePath + " to " + getTmpPath() + ": " + - result.getLogMessage(), e); - } - - return result; - } - - private int downloadFile(OwnCloudClient client, File targetFile) throws IOException, OperationCancelledException { - int status = -1; - boolean savedFile = false; - mGet = new GetMethod(client.getWebdavUri() + WebdavUtils.encodePath(mRemotePath)); - - FileOutputStream fos = null; - try { - status = client.executeMethod(mGet); - if (isSuccess(status)) { - targetFile.createNewFile(); - BufferedInputStream bis = new BufferedInputStream(mGet.getResponseBodyAsStream()); - fos = new FileOutputStream(targetFile); - long transferred = 0; - - Header contentLength = mGet.getResponseHeader("Content-Length"); - long totalToTransfer = (contentLength != null && - contentLength.getValue().length() > 0) ? - Long.parseLong(contentLength.getValue()) : 0; - - byte[] bytes = new byte[4096]; - int readResult = 0; - while ((readResult = bis.read(bytes)) != -1) { - synchronized (mCancellationRequested) { - if (mCancellationRequested.get()) { - mGet.abort(); - throw new OperationCancelledException(); - } - } - fos.write(bytes, 0, readResult); - transferred += readResult; - } - // Check if the file is completed - // if transfer-encoding: chunked we cannot check if the file is complete - Header transferEncodingHeader = mGet.getResponseHeader("Transfer-Encoding"); - boolean transferEncoding = false; - - if (transferEncodingHeader != null) { - transferEncoding = transferEncodingHeader.getValue().equals("chunked"); - } - - if (transferred == totalToTransfer || transferEncoding) { - savedFile = true; - Header modificationTime = mGet.getResponseHeader("Last-Modified"); - if (modificationTime == null) { - modificationTime = mGet.getResponseHeader("last-modified"); - } - if (modificationTime != null) { - Date d = WebdavUtils.parseResponseDate(modificationTime.getValue()); - mModificationTimestamp = (d != null) ? d.getTime() : 0; - } else { - Log_OC.e(TAG, "Could not read modification time from response downloading " + mRemotePath); - } - - mEtag = WebdavUtils.getEtagFromResponse(mGet); - if (mEtag.length() == 0) { - Log_OC.e(TAG, "Could not read eTag from response downloading " + mRemotePath); - } - - } else { - client.exhaustResponse(mGet.getResponseBodyAsStream()); - // TODO some kind of error control! - } - } else { - client.exhaustResponse(mGet.getResponseBodyAsStream()); - } - } catch (Exception e) { - Log_OC.e(TAG, e.getMessage()); - } finally { - if (fos != null) fos.close(); - if (!savedFile && targetFile.exists()) { - targetFile.delete(); - } - mGet.releaseConnection(); // let the connection available for other methods - } - return status; - } - - private boolean isSuccess(int status) { - return (status == HttpStatus.SC_OK); - } - - /** - * IMPLEMENTATION DIFFER FROM NC IMPLEMENTATION - * @return - */ - private String getTmpPath() { - return mLocalFolderPath; - } -} diff --git a/app/src/main/java/foundation/e/drive/operations/GetAliasOperation.java b/app/src/main/java/foundation/e/drive/operations/GetAliasOperation.java index 23139360b4c22faf5f12a03a61d254ee690fe73b..c73473d076e443cf28b05907aa0021adabca18f7 100644 --- a/app/src/main/java/foundation/e/drive/operations/GetAliasOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/GetAliasOperation.java @@ -17,12 +17,14 @@ import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.methods.GetMethod; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; /** * @author TheScarastic + * /!\ Doesn't require NextcloudClient yet */ public class GetAliasOperation extends RemoteOperation { @@ -34,7 +36,6 @@ public class GetAliasOperation extends RemoteOperation { private static final String NODE_OCS = "ocs"; private static final String NODE_DATA = "data"; private static final String NODE_ALIASES = "aliases"; - private final String mID; private boolean isSuccess(int status) { @@ -46,8 +47,8 @@ public class GetAliasOperation extends RemoteOperation { } @Override - protected RemoteOperationResult run(OwnCloudClient client) { - RemoteOperationResult result; + protected RemoteOperationResult> run(OwnCloudClient client) { + RemoteOperationResult> result; GetMethod get = null; final String uri = client.getBaseUri() + ALIAS_PATH + mID; @@ -57,25 +58,26 @@ public class GetAliasOperation extends RemoteOperation { get.setQueryString(new NameValuePair[]{new NameValuePair("format", "json")}); if (isSuccess(client.executeMethod(get))) { - String response = get.getResponseBodyAsString(); + final String response = get.getResponseBodyAsString(); // parse - final JSONArray aliases = new JSONObject(response).getJSONObject(NODE_OCS) - .getJSONObject(NODE_DATA).getJSONArray(NODE_ALIASES); - final ArrayList resultAliases = new ArrayList<>(); - for (int i = 0; i < aliases.length(); i++) { - resultAliases.add(aliases.get(i)); - } - result = new RemoteOperationResult(true, get); - result.setData(resultAliases); + final JSONArray aliases = parseResponse(response); + final ArrayList resultAliases = new ArrayList<>(); + result = new RemoteOperationResult<>(true, get); + if (aliases != null) { + for (int i = 0; i < aliases.length(); i++) { + resultAliases.add(aliases.get(i).toString()); + } + } + result.setResultData(resultAliases); } else { - result = new RemoteOperationResult(false, get); + result = new RemoteOperationResult<>(false, get); } } catch (Exception e) { e.printStackTrace(); - result = new RemoteOperationResult(e); + result = new RemoteOperationResult<>(e); Log_OC.e(TAG, "Fetching aliases failed"); } finally { if (get != null) @@ -84,4 +86,21 @@ public class GetAliasOperation extends RemoteOperation { return result; } + + + public JSONArray parseResponse(String response) { + JSONArray result = null; + try { + final JSONObject jsonResponse= new JSONObject(response).optJSONObject(NODE_OCS); + if (jsonResponse == null) return result; + + final JSONObject jsonData = jsonResponse.optJSONObject(NODE_DATA); + if (jsonData == null) return result; + + result = jsonData.optJSONArray(NODE_ALIASES); + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } } diff --git a/app/src/main/java/foundation/e/drive/operations/ListFileRemoteOperation.java b/app/src/main/java/foundation/e/drive/operations/ListFileRemoteOperation.java index b35bd137efbe91826818a6e112d60e8b362db478..29dd762b07266176acddfa280d67e3626b09c1c6 100644 --- a/app/src/main/java/foundation/e/drive/operations/ListFileRemoteOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/ListFileRemoteOperation.java @@ -14,8 +14,8 @@ import android.util.Log; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation; import com.owncloud.android.lib.resources.files.model.RemoteFile; -import com.owncloud.android.lib.resources.files.LightReadFolderRemoteOperation; import java.io.File; import java.util.ArrayList; import java.util.List; @@ -24,22 +24,20 @@ import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.utils.CommonUtils; -import static org.apache.jackrabbit.webdav.DavConstants.DEPTH_1; /** - * + * /!\ Doesn't require NextcloudClient yet * @author Vincent Bourgmayer - * Created by Vincent on 04/05/2018. + * Created on 04/05/2018. */ -public class ListFileRemoteOperation extends RemoteOperation { +public class ListFileRemoteOperation extends RemoteOperation> { private final String TAG = ListFileRemoteOperation.class.getSimpleName(); private final List mSyncedFolders; private final Context mContext; private final int initialFolderNumber; - public ListFileRemoteOperation(List syncedFolders, Context context, int initialFolderNumber){ - Log.i(TAG, "Constructor of ListFileRemoteOperation"); + public ListFileRemoteOperation(List syncedFolders, Context context, int initialFolderNumber) { this.mSyncedFolders = syncedFolders; this.mContext = context; this.initialFolderNumber = initialFolderNumber; @@ -51,146 +49,102 @@ public class ListFileRemoteOperation extends RemoteOperation { * @return List containing remoteFolder followed by remote files */ @Override - protected RemoteOperationResult run(OwnCloudClient ownCloudClient){ + protected RemoteOperationResult> run(OwnCloudClient ownCloudClient) { Log.i(TAG, "run()"); - ArrayList mRemoteFiles = new ArrayList<>(); + final ArrayList mRemoteFiles = new ArrayList<>(); RemoteOperationResult finalResult; boolean atLeastOneDirAsChanged = false; - ListIterator mSyncedFolderIterator = mSyncedFolders.listIterator(); + final ListIterator mSyncedFolderIterator = mSyncedFolders.listIterator(); - //Loop through list of SyncedFolder - while (mSyncedFolderIterator.hasNext() ){ - - //Get CurrentSyncedFolder - SyncedFolder syncedFolder = mSyncedFolderIterator.next(); - - //if folder is media type() && is an hidden folder then ignore it - String fileName = CommonUtils.getFileNameFromPath(syncedFolder.getRemoteFolder()); - if (fileName == null) { - Log.e(TAG, "getFileNameFromPath() returned null. Returning to prevent a NPE"); - return new RemoteOperationResult(RemoteOperationResult.ResultCode.UNKNOWN_ERROR); - } - - if(syncedFolder.isMediaType() - && fileName.startsWith(".")){ - mSyncedFolderIterator.remove(); - continue; + while (mSyncedFolderIterator.hasNext()) { + try { + Thread.sleep(150); + } catch (InterruptedException e) { + Log.w(TAG, "listFileRemoteOperation's sleep had been interrupted"); } - //If folder isn't to be scan remotly, ignore it - if(!syncedFolder.isScanRemote()) { + final SyncedFolder syncedFolder = mSyncedFolderIterator.next(); + if (shouldSkipSyncedFolder(syncedFolder)) { mSyncedFolderIterator.remove(); continue; } - if(syncedFolder.getId() == -1) { - //persist new syncedFolder - int syncedFolderId = (int) DbHelper.insertSyncedFolder(syncedFolder, mContext); + if (syncedFolder.getId() == -1) { + final int syncedFolderId = (int) DbHelper.insertSyncedFolder(syncedFolder, mContext); if (syncedFolderId > 0) { syncedFolder.setId(syncedFolderId); - }else{ + } else { mSyncedFolderIterator.remove(); - Log.w(TAG, "syncedFolder "+syncedFolder.getRemoteFolder()+" doesn't have a valid ID"); + Log.w(TAG, "syncedFolder " + syncedFolder.getRemoteFolder() + " doesn't have a valid ID"); continue; } } - //Create ReadRemoteOperation - LightReadFolderRemoteOperation operation = new LightReadFolderRemoteOperation(syncedFolder.getRemoteFolder(), DEPTH_1, false); - RemoteOperationResult result = operation.execute(ownCloudClient); - - if(result.isSuccess() ){ - //is success then data can't be null - int dataSize = result.getData().size(); - if(dataSize > 1){ //There is at least one subfiles - RemoteFile directory = (RemoteFile) result.getData().get(0); - if(!directory.getEtag().equals(syncedFolder.getLastEtag() )){ //if etag differs - List remoteFiles = result.getData().subList( 1, dataSize ); //get list of subfiles - - //loop through subelements - for (int i = -1, remoteFilesSize = remoteFiles.size(); ++i < remoteFilesSize; ){ - RemoteFile remoteFile = (RemoteFile) remoteFiles.get(i); - - //if remoteFile is in a "media" folder and its name start with "." - // then ignore it - fileName = CommonUtils.getFileNameFromPath(remoteFile.getRemotePath()); - if (fileName == null) { - Log.e(TAG, "getFileNameFromPath() returned null. Returning to prevent NPE"); - return new RemoteOperationResult(RemoteOperationResult.ResultCode.UNKNOWN_ERROR); - } - - if(syncedFolder.isMediaType() && fileName.startsWith(".") ){ - continue; - } - if( remoteFile.getMimeType().equals("DIR") ) { - String suffixPath = remoteFile.getRemotePath().substring( syncedFolder.getRemoteFolder().length() ); - - //but is it already known as SyncedFolder? - SyncedFolder subSyncedFolder = new SyncedFolder(syncedFolder, suffixPath, 0L, "" ); //need to set empty etag to allow it to be scan - mSyncedFolderIterator.add(subSyncedFolder); - mSyncedFolderIterator.previous(); - - }else { - //If it's a file just add it to mRemoteFiles. - mRemoteFiles.add(remoteFile); - } + final ReadFolderRemoteOperation operation = new ReadFolderRemoteOperation(syncedFolder.getRemoteFolder()); + final RemoteOperationResult result = operation.execute(ownCloudClient); + + if (result.isSuccess()) { + final int dataSize = result.getData().size(); + final RemoteFile directory = (RemoteFile) result.getData().get(0); + + if (directory.getEtag().equals(syncedFolder.getLastEtag())) { + continue; + } + syncedFolder.setLastEtag(directory.getEtag()).setToSync(true); + atLeastOneDirAsChanged = true; + + if (dataSize > 1) { + final List remoteFiles = result.getData().subList(1, dataSize); //get list of subfiles + + //loop through subelements + for (int i = -1, remoteFilesSize = remoteFiles.size(); ++i < remoteFilesSize; ) { + final RemoteFile remoteFile = (RemoteFile) remoteFiles.get(i); + + if (remoteFile.getMimeType().equals("DIR")) { + final String suffixPath = remoteFile.getRemotePath().substring(syncedFolder.getRemoteFolder().length()); + final SyncedFolder subSyncedFolder = new SyncedFolder(syncedFolder, suffixPath, 0L, ""); //need to set empty etag to allow it to be scan + mSyncedFolderIterator.add(subSyncedFolder); + mSyncedFolderIterator.previous(); + } else { + mRemoteFiles.add(remoteFile); } - syncedFolder.setLastEtag(directory.getEtag() ).setToSync(true); - atLeastOneDirAsChanged = true; - } - }else if(dataSize == 1){ //Empty folder - RemoteFile directory = (RemoteFile) result.getData().get(0); - if(!directory.getEtag().equals(syncedFolder.getLastEtag())) { - syncedFolder.setLastEtag(directory.getEtag()).setToSync(true); - atLeastOneDirAsChanged = true; } - }//Last else correspond to error 404 at LightReadFolderRemoteOperation (see below) - }else{ //Result isn't a success - if(result.getHttpCode() == 404){ //File not found + } + }else { //Result isn't a success + if (result.getHttpCode() == 404) { //File not found atLeastOneDirAsChanged = true; syncedFolder.setToSync(true); - //If there is no remote file, then try to delete local one if empty. Finally remove Synced Folder from DB. - File localFolder = new File(syncedFolder.getLocalFolder()); - if (localFolder.exists()) { - File[] arrayFiles = localFolder.listFiles(); - if (arrayFiles != null && arrayFiles.length == 0) { - localFolder.delete(); - } - } - if( !localFolder.exists() ) { - if (syncedFolder.getId() > this.initialFolderNumber/*-1*/) { //does the synced folder has been persisted? - //remove it from DB - int deleteResult = DbHelper.deleteSyncedFolder(syncedFolder.getId(), mContext); - Log.d(TAG, "syncedFolder Id: "+syncedFolder.getId() + " deletion from db return " + deleteResult + " row affected"); + final File localFolder = new File(syncedFolder.getLocalFolder()); + if (!localFolder.exists()) { + if (syncedFolder.getId() > this.initialFolderNumber) { + final int deleteResult = DbHelper.deleteSyncedFolder(syncedFolder.getId(), mContext); + Log.d(TAG, "syncedFolder Id: " + syncedFolder.getId() + " deletion from db return " + deleteResult + " row affected"); } mSyncedFolderIterator.remove(); + } else if (localFolder.listFiles().length == 0) { + localFolder.delete(); } } - Log.w(TAG, "LightReadFolderRemoteOperation failed : http " + result.getHttpCode() + ", " + result.getLogMessage()+" => Ignored"); - } - - - try{ - Thread.sleep(150); - }catch(InterruptedException e){ - Log.w(TAG, "listFileRemoteOperation's sleep had been interrupted"); + Log.w(TAG, "ReadFolderRemoteOperation failed : http " + result.getHttpCode() + ", " + result.getLogMessage() + " => Ignored"); } - - } //End of loop - finalResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.OK); - - if( atLeastOneDirAsChanged ) { + } + finalResult = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.OK); + if (atLeastOneDirAsChanged) { DbHelper.updateSyncedFolders(this.mSyncedFolders, this.mContext); - finalResult.setData(new ArrayList(mRemoteFiles) ); + finalResult.setResultData(mRemoteFiles); } - - Log.v(TAG, "end of run()"); return finalResult; } - /** + private boolean shouldSkipSyncedFolder(SyncedFolder syncedFolder) { + return (syncedFolder.isMediaType() + && CommonUtils.getFileNameFromPath(syncedFolder.getRemoteFolder()).startsWith(".")) + || !syncedFolder.isScanRemote(); + } + + /** * * @return list of syncedFolder */ diff --git a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java index c1eed28e83676d709c610018f3048c19fc94c252..4cfc10b41ff698d3985a4807a44225304953ea7e 100644 --- a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java @@ -9,11 +9,13 @@ package foundation.e.drive.operations; +import android.accounts.Account; import android.content.Context; import android.util.Log; import androidx.annotation.VisibleForTesting; +import com.nextcloud.common.NextcloudClient; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.UserInfo; import com.owncloud.android.lib.common.operations.RemoteOperation; @@ -21,13 +23,13 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation; import com.owncloud.android.lib.resources.files.FileUtils; import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation; -import com.owncloud.android.lib.resources.users.GetRemoteUserInfoOperation; +import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import java.io.File; -import java.util.ArrayList; import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DavClientProvider; /** * @author Vincent Bourgmayer @@ -38,18 +40,19 @@ public class UploadFileOperation extends RemoteOperation { private int restartCounter =0; private long previousLastModified; //get to restore real value if all trials fails - private Context context; - private SyncedFileState syncedState; - private long availableQuota = -1; + private final Context context; + private final SyncedFileState syncedState; + private final Account account; // /!\ this is temporary because NC library doesn't use NextcloudClient for every operation yet /** * Construct an upload operation with an already known syncedFileState * @param syncedFileState syncedFileState corresponding to file. */ - public UploadFileOperation (SyncedFileState syncedFileState, Context context) { + public UploadFileOperation (final SyncedFileState syncedFileState, final Account account, final Context context) { this.syncedState = syncedFileState; this.previousLastModified = syncedState.getLocalLastModified(); this.context = context; + this.account = account; } /** @@ -65,13 +68,7 @@ public class UploadFileOperation extends RemoteOperation { */ @Override protected RemoteOperationResult run(OwnCloudClient client ) { - //as operation isn't executed immediatly, file might have been deleted since creation of operation - if (syncedState == null ) { - Log.e(TAG, "run(client): no syncedFileState or target path, can't perform upload operation"); - return new RemoteOperationResult(ResultCode.FORBIDDEN); - } - - File file = new File(syncedState.getLocalPath()); + final File file = new File(syncedState.getLocalPath()); if (file == null || !file.exists()) { Log.w(TAG, "Can't get the file. It might have been deleted"); return new RemoteOperationResult(ResultCode.FORBIDDEN); @@ -79,39 +76,32 @@ public class UploadFileOperation extends RemoteOperation { final String targetPath = syncedState.getRemotePath(); - //If an Etag is already Stored and LastModified from DB is the same as real file + //If file already up-to-date & synced if (syncedState.isLastEtagStored() && syncedState.getLocalLastModified() == file.lastModified()) { Log.d(TAG, "syncedState last modified: "+ syncedState.getLocalLastModified()+" <=> file last modified: "+file.lastModified() +": So return sync_conflict"); return new RemoteOperationResult(ResultCode.SYNC_CONFLICT); } - - Float relativeQuotaBeforeFileUpload = 0.0f; - if (this.availableQuota == -1) { - RemoteOperationResult checkQuotaResult = checkAvailableSpace(client, file.length()); - if (checkQuotaResult.getCode() != ResultCode.OK) { - return new RemoteOperationResult(checkQuotaResult.getCode()); - } else { - relativeQuotaBeforeFileUpload = ((Double) checkQuotaResult.getSingleData()).floatValue(); - } + final NextcloudClient ncClient = DavClientProvider.getInstance().getNcClientInstance(account, context); + final RemoteOperationResult checkQuotaResult = checkAvailableSpace(ncClient, file.length()); + if (checkQuotaResult.getCode() != ResultCode.OK) { + Log.e(TAG, "Impossible to check quota. Upload of " + syncedState.getLocalPath() + "cancelled"); + return new RemoteOperationResult(checkQuotaResult.getCode()); } if (!createRemoteFolder(targetPath, client)) { return new RemoteOperationResult(ResultCode.UNKNOWN_ERROR); } - final UploadFileRemoteOperation uploadOperation = buildUploadOperation(file, targetPath); - // Execute UploadFileOperation - RemoteOperationResult uploadResult = uploadOperation.execute(client ); - ResultCode resultCode; + final ResultCode resultCode; boolean mustRestart = true; - //if upload is a success + final RemoteOperationResult uploadResult = uploadFile(file, client); if (uploadResult.isSuccess()) { - Object data = uploadResult.getSingleData(); - if (data != null) { - syncedState.setLastETAG((String) data); + final String etag = uploadResult.getResultData(); + if (etag != null) { + syncedState.setLastETAG(etag); } syncedState.setLocalLastModified(file.lastModified()); resultCode = uploadResult.getCode(); @@ -125,7 +115,6 @@ public class UploadFileOperation extends RemoteOperation { resultCode = ResultCode.QUOTA_EXCEEDED; mustRestart = false; } else { - //Upload failed Log.e(TAG, "UploadFileRemoteOperation for : " + file.getName() + " failed => code: " + uploadResult.getCode()); resultCode = ResultCode.UNKNOWN_ERROR; mustRestart = false; @@ -135,38 +124,13 @@ public class UploadFileOperation extends RemoteOperation { if (mustRestart) { if (this.restartCounter < 1) { this.restartCounter += 1; - //if we encounter more than one time same error, stop trying to upload. return this.run(client); } else { syncedState.setLocalLastModified(this.previousLastModified); //Revert syncFileState to its previous state } } - // updated syncedFile in DB DbHelper.manageSyncedFileStateDB(syncedState, "UPDATE", context); - - ArrayList datas = new ArrayList<>(); - datas.add(syncedState.getSyncedFolderId()); - datas.add(relativeQuotaBeforeFileUpload); - final RemoteOperationResult finalResult = new RemoteOperationResult(resultCode); - finalResult.setData(datas); - return finalResult; - } - - /** - * Build the operation to put the file on server - * @return the operation to execute - */ - private UploadFileRemoteOperation buildUploadOperation(File file, String targetPath) { - String timeStamp = ((Long) (file.lastModified() / 1000) ).toString() ; - - //create UploadFileOperation - UploadFileRemoteOperation uploadRemoteFileOperation = new UploadFileRemoteOperation(syncedState.getLocalPath(), - (targetPath != null ) ? targetPath : syncedState.getRemotePath(), - CommonUtils.getMimeType(file ), - (!this.syncedState.isMediaType() || syncedState.getLastETAG().isEmpty() )? null : syncedState.getLastETAG(), //If not null, This can cause error 412; that means remote file has change - timeStamp ); - uploadRemoteFileOperation.askResultEtag(true); - return uploadRemoteFileOperation; + return new RemoteOperationResult(resultCode); } /** @@ -175,32 +139,52 @@ public class UploadFileOperation extends RemoteOperation { * @return RemoteOperationResult */ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - public RemoteOperationResult checkAvailableSpace(OwnCloudClient client, long fileSize) { - GetRemoteUserInfoOperation getRemoteUserInfoOperation = new GetRemoteUserInfoOperation(); - RemoteOperationResult ocsResult = getRemoteUserInfoOperation.execute(client); - if (ocsResult.isSuccess() && ocsResult.getData() != null) { - UserInfo userInfo = (UserInfo) ocsResult.getData().get(0); - this.availableQuota = userInfo.getQuota().getFree(); - if (((UserInfo) ocsResult.getData().get(0)).getQuota().getFree() < fileSize ) { - Log.w(TAG, "quota exceeded!"); - return new RemoteOperationResult(ResultCode.QUOTA_EXCEEDED); - } else { - Log.d(TAG, "Quota Okay"); - RemoteOperationResult result = new RemoteOperationResult(ResultCode.OK); - result.setSingleData((Double) userInfo.getQuota().getRelative()); - return result; - } + public RemoteOperationResult checkAvailableSpace(NextcloudClient client, long fileSize) { + final RemoteOperationResult ocsResult = readUserInfo(client); + final ResultCode resultCode; + if (ocsResult.isSuccess() && ocsResult.getResultData().getQuota().getFree() < fileSize) { + resultCode = ResultCode.QUOTA_EXCEEDED; } else { - Log.w(TAG, "getRemoteUserInfoOperation failed: "+ocsResult.getHttpCode() ); - return new RemoteOperationResult(ocsResult.getCode()); + resultCode = ocsResult.getCode(); } + return new RemoteOperationResult(resultCode); } + /** + * Read user info to get Quota's data + * note: this has been extracted from checkAvailableSpace for + * testing purpose + * @param client client to run the method + * @return RemoteOperationResult + */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + public RemoteOperationResult readUserInfo(NextcloudClient client) { + final GetUserInfoRemoteOperation GetUserInfoRemoteOperation = new GetUserInfoRemoteOperation(); + return GetUserInfoRemoteOperation.execute(client); + } + + /** + * Effectively upload the file + * note: this has been extracted from run(...) for + * testing purpose + * @param client client to run the method + * @return RemoteOperationResult + */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + public RemoteOperationResult uploadFile(final File file, final OwnCloudClient client) { + final String timeStamp = ((Long) (file.lastModified() / 1000) ).toString() ; + final UploadFileRemoteOperation uploadOperation = new UploadFileRemoteOperation(syncedState.getLocalPath(), + syncedState.getRemotePath(), + CommonUtils.getMimeType(file), + (!this.syncedState.isMediaType() || syncedState.getLastETAG().isEmpty())? null : syncedState.getLastETAG(), //If not null, This can cause error 412; that means remote file has change + timeStamp ); + return uploadOperation.execute(client); + } /** * Create remote parent folder of the file if missing * @param targetPath - * @param client + * @param client still OwnCloudClient at the moment, but will be Nextcloud client in the futur * @return */ public boolean createRemoteFolder(String targetPath, OwnCloudClient client) { @@ -217,4 +201,8 @@ public class UploadFileOperation extends RemoteOperation { } return false; } + + public SyncedFileState getSyncedState() { + return syncedState; + } } diff --git a/app/src/main/java/foundation/e/drive/services/InitializerService.java b/app/src/main/java/foundation/e/drive/services/InitializerService.java index 922e199075b6213a49792984d0d3f4ab28e8d94c..b53fc3207f0a361e9acbb4644e05399bccae8657 100644 --- a/app/src/main/java/foundation/e/drive/services/InitializerService.java +++ b/app/src/main/java/foundation/e/drive/services/InitializerService.java @@ -29,6 +29,7 @@ import java.util.List; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DavClientProvider; import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; import static foundation.e.drive.utils.AppConstants.MEDIA_SYNCABLE_CATEGORIES; @@ -85,7 +86,7 @@ public class InitializerService extends Service { this.account = CommonUtils.getAccount(accountName, accountType, AccountManager.get(this)); //Get OwnCloudlient if (this.account != null) { - this.cloudClient = CommonUtils.getOwnCloudClient(this.account, getApplicationContext()); + this.cloudClient = DavClientProvider.getInstance().getClientInstance(account, getApplicationContext()); start(); } else { Log.w(TAG, "Got account is invalid."); diff --git a/app/src/main/java/foundation/e/drive/services/ObserverService.java b/app/src/main/java/foundation/e/drive/services/ObserverService.java index 38cc3b5c19dd65adff8d6ccb0bd8d7c1b07b7f43..133abbabdfaa43fa523b7355579ba20410b01947 100644 --- a/app/src/main/java/foundation/e/drive/services/ObserverService.java +++ b/app/src/main/java/foundation/e/drive/services/ObserverService.java @@ -91,7 +91,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene CommonUtils.setServiceUnCaughtExceptionHandler(this); - SharedPreferences prefs = this.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + final SharedPreferences prefs = this.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); String accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, ""); String accountType = prefs.getString(AccountManager.KEY_ACCOUNT_TYPE, ""); this.mAccount = CommonUtils.getAccount(accountName, accountType, AccountManager.get(this)); @@ -243,8 +243,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene OwnCloudClient client = DavClientProvider.getInstance().getClientInstance(mAccount, getApplicationContext()); if (client != null) { try { - Log.d(TAG, "Going to scan remote files"); - ListFileRemoteOperation loadOperation = new ListFileRemoteOperation(this.mSyncedFolders, this, this.initialFolderCounter); + final ListFileRemoteOperation loadOperation = new ListFileRemoteOperation(this.mSyncedFolders, this, this.initialFolderCounter); loadOperation.execute(client, this, new Handler()); } catch (IllegalArgumentException e){ Log.e(TAG, e.toString() ); @@ -258,7 +257,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene } } - /** * Get list of synced folder depending of if media and setting sync are enabled. * @return @@ -286,43 +284,44 @@ public class ObserverService extends Service implements OnRemoteOperationListene @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result ) { Log.i( TAG, "onRemoteOperationFinish()" ); - if ( operation instanceof ListFileRemoteOperation) { - if (result.isSuccess()) { - List resultDatas = result.getData(); - if (resultDatas != null) { - mSyncedFolders = ((ListFileRemoteOperation) operation).getSyncedFolderList(); - List syncedFileStateList = DbHelper.getSyncedFileStatesByFolders(this, - getIdsFromFolderToScan()); - - //At least one list is not empty - if (!resultDatas.isEmpty() || !syncedFileStateList.isEmpty() && CommonUtils.isMediaSyncEnabled(mAccount)) { - handleRemoteFiles(resultDatas, syncedFileStateList); - } else { - Log.v(TAG, "No remote files nor syncedFileStates. Go next step"); - } + if ( ! (operation instanceof ListFileRemoteOperation)) { return;} + if (result.isSuccess()) { + final List remoteFiles = ((RemoteOperationResult>)result).getResultData(); + if (remoteFiles != null) { + final ListFileRemoteOperation listFileOperation = (ListFileRemoteOperation) operation; + mSyncedFolders = listFileOperation.getSyncedFolderList(); + final List syncedFileStateList = DbHelper.getSyncedFileStatesByFolders(this, + getIdsFromFolderToScan()); + + //At least one list is not empty + if (!remoteFiles.isEmpty() || !syncedFileStateList.isEmpty() && CommonUtils.isMediaSyncEnabled(mAccount)) { + handleRemoteFiles(remoteFiles, syncedFileStateList); + } else { + Log.v(TAG, "No remote files nor syncedFileStates. Go next step"); } - } else { - Log.w(TAG, "ListRemoteFileOperation doesn't return a success: " + result.getHttpCode()); } - this.startScan(false); - - Log.v(TAG, "operationsForIntent contains " + syncRequests.size()); + } else { + Log.w(TAG, "ListRemoteFileOperation doesn't return a success: " + result.getHttpCode()); + } + this.startScan(false); + Log.v(TAG, "operationsForIntent contains " + syncRequests.size()); - //After everything has been scanned. Send Intent to OperationmanagerService with data in bundle - if (syncRequests != null && !syncRequests.isEmpty()) { - passSyncRequestsToSynchronizationService(); - } else { - Log.w(TAG, "There is no file to sync."); - getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) - .edit() - .putLong(AppConstants.KEY_LAST_SYNC_TIME, System.currentTimeMillis()) - .apply(); - } - this.isWorking = false; - this.stopSelf(); - } + if (syncRequests != null && !syncRequests.isEmpty()) { + passSyncRequestsToSynchronizationService(); + } else { + Log.w(TAG, "There is no file to sync."); + getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) + .edit() + .putLong(AppConstants.KEY_LAST_SYNC_TIME, System.currentTimeMillis()) + .apply(); } + this.isWorking = false; + this.stopSelf(); + } + /** + * Send all gathered SyncRequest to SynchronizationService + */ private void passSyncRequestsToSynchronizationService() { if (synchronizationServiceConnection.isBoundToSynchronizationService()) { synchronizationServiceConnection.getSynchronizationService().queueSyncRequests(syncRequests.values()); @@ -356,14 +355,14 @@ public class ObserverService extends Service implements OnRemoteOperationListene * @param remoteFiles Remote Files to inspect * @param syncedFileStates SyncedFileState to inspect */ - private void handleRemoteFiles(List remoteFiles, List syncedFileStates ){ + private void handleRemoteFiles(List remoteFiles, List syncedFileStates ){ Log.i(TAG, "handleRemoteFiles()"); Log.d(TAG, "start to loop through remoteFiles"); ListIterator syncedFileListIterator; for( int i =-1, size = remoteFiles.size(); ++i < size; ){ - final RemoteFile remoteFile = (RemoteFile) remoteFiles.get(i); + final RemoteFile remoteFile = remoteFiles.get(i); final String remoteFilePath = remoteFile.getRemotePath(); // hidden file from server has already been filtered in previous step @@ -735,7 +734,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene } //end of test if folder path match with file's parent path }//end of loop over folder }//end of loop over local files - //Handle remaining file handleLocalRemainingSyncedFileState( syncedFileStates ); } diff --git a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java index da005cc93f9afa400b9d5e37e40bbf01f70795a1..020429da75d5b01dc65db3eb99f0d404c47c0c43 100644 --- a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java +++ b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java @@ -22,12 +22,12 @@ import android.util.Log; import androidx.annotation.Nullable; +import com.nextcloud.common.NextcloudClient; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -56,7 +56,9 @@ public class SynchronizationService extends Service implements OnRemoteOperation private Account account; private final int workerAmount = 4; private Thread[] threadPool; - private OwnCloudClient client; + @Deprecated + private OwnCloudClient ocClient; + private NextcloudClient ncClient; private Handler handler; @Override @@ -77,7 +79,8 @@ public class SynchronizationService extends Service implements OnRemoteOperation syncRequestQueue = new ConcurrentLinkedDeque<>(); startedSync = new ConcurrentHashMap<>(); threadPool = new Thread[workerAmount]; - client = DavClientProvider.getInstance().getClientInstance(account, getApplicationContext()); + ocClient = DavClientProvider.getInstance().getClientInstance(account, getApplicationContext()); + ncClient = DavClientProvider.getInstance().getNcClientInstance(account, getApplicationContext()); handler = new Handler(); return START_REDELIVER_INTENT; @@ -152,7 +155,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation final SyncRequest request = this.syncRequestQueue.poll(); //return null if empty if (request == null) return; - final SyncWrapper syncWrapper = new SyncWrapper(request, getApplicationContext()); + final SyncWrapper syncWrapper = new SyncWrapper(request, account, getApplicationContext()); final RemoteOperation operation = syncWrapper.getRemoteOperation(); if (operation != null) { @@ -160,8 +163,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation CommonUtils.createNotificationChannel(this); Log.v(TAG, " starts " + request.getSyncedFileState().getName() + " " + request.getOperationType().name() + " on thread " + threadIndex); - - threadPool[threadIndex] = operation.execute(client, this, handler); + threadPool[threadIndex] = operation.execute(ocClient, this, handler); startedSync.put(threadIndex, syncWrapper); } } @@ -174,7 +176,6 @@ public class SynchronizationService extends Service implements OnRemoteOperation */ private boolean canStart(int threadIndex) { final boolean meteredNetworkAllowed = CommonUtils.isMeteredNetworkAllowed(account); - final SyncWrapper syncWrapper = startedSync.get(threadIndex); if ((syncWrapper != null && syncWrapper.isRunning()) @@ -201,19 +202,13 @@ public class SynchronizationService extends Service implements OnRemoteOperation updateFailureCounter(callerWrapper.getRequest(), result.isSuccess()); } - - if (callerOperation instanceof RemoveFileOperation){ + if (callerOperation instanceof RemoveFileOperation) { if ( result.isSuccess() ) { DbHelper.manageSyncedFileStateDB( ( ( RemoveFileOperation ) callerOperation ).getSyncedFileState(), "DELETE", this); } } else { - String operationClassName = callerOperation.getClass().getSimpleName(); - final ArrayList callerOperationResultData = result.getData(); - if (callerOperation instanceof UploadFileOperation && callerOperationResultData != null - && callerOperationResultData.size() > 1) { - final Float relativeQuota = (Float) result.getData().get(1); - } + final String operationClassName = callerOperation.getClass().getSimpleName(); switch (result.getCode()) { case OK: Log.d(TAG, operationClassName + " Succeed"); @@ -227,24 +222,16 @@ public class SynchronizationService extends Service implements OnRemoteOperation break; case UNKNOWN_ERROR: if (callerOperation instanceof UploadFileOperation) { - if (callerOperationResultData != null && callerOperationResultData.size() > 0) { - int rowAffected = DbHelper.forceFoldertoBeRescan(((Long) callerOperationResultData.get(0)).intValue(), getApplicationContext()); - Log.e(TAG, " Upload failed for unknown reason.\n Force folder to be rescan next time (row affected) :" + rowAffected); - } else { - Log.w(TAG, "result.getData() for UploadFileOperation returned null"); - } + final int rowAffected = DbHelper.forceFoldertoBeRescan(((UploadFileOperation) callerOperation).getSyncedState().getId(), getApplicationContext()); + Log.e(TAG, " Upload failed for unknown reason.\n Force folder to be rescan next time (row affected) :" + rowAffected); } else if (callerOperation instanceof DownloadFileOperation) { Log.e(TAG, " Download: Unknown_error : failed"); } break; case FORBIDDEN: if (callerOperation instanceof UploadFileOperation) { - if (callerOperationResultData != null && callerOperationResultData.size() > 0) { - int rowAffected = DbHelper.forceFoldertoBeRescan(((Long) callerOperationResultData.get(0)).intValue(), getApplicationContext()); - Log.e(TAG, " Upload: Forbidden : Can't get syncedFileState, no remote path defined. Force folder to be rescan next time (row affected) :" + rowAffected); - } else { - Log.w(TAG, "result.getData() for UploadFileOperation returned null"); - } + final int rowAffected = DbHelper.forceFoldertoBeRescan(((UploadFileOperation) callerOperation).getSyncedState().getId(), getApplicationContext()); + Log.e(TAG, " Upload: Forbidden : Can't get syncedFileState, no remote path defined. Force folder to be rescan next time (row affected) :" + rowAffected); } else if (callerOperation instanceof DownloadFileOperation) { Log.e(TAG, "Download : Forbidden: Can't get syncedFileState, no local path defined"); } diff --git a/app/src/main/java/foundation/e/drive/utils/AppConstants.java b/app/src/main/java/foundation/e/drive/utils/AppConstants.java index 08fae5a10fef3ed3bcb24230468e4f2a8f3eea54..441924e398a337167d80c2a59b152c2e4f247a43 100644 --- a/app/src/main/java/foundation/e/drive/utils/AppConstants.java +++ b/app/src/main/java/foundation/e/drive/utils/AppConstants.java @@ -39,7 +39,7 @@ public abstract class AppConstants { public static final String ACCOUNT_DATA_GROUPS = "group"; public static final String ACCOUNT_DATA_ALIAS_KEY = "alias"; public static final String ACCOUNT_DATA_EMAIL = "email"; - + public static final String ACCOUNT_USER_ID_KEY = "USERID"; public final static String FAILED_TRANSFER_PREF = "failed_transfer"; public static final String[] MEDIA_SYNCABLE_CATEGORIES = new String[]{"Images", "Movies", "Music", "Ringtones", "Documents", "Podcasts"}; public static final String[] SETTINGS_SYNCABLE_CATEGORIES = new String[]{"Rom settings"}; diff --git a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java index b9a12691bd824818c2771e73e132aa4cfe3f6052..c909dc6dc79b200e126181498f7ae583a6e5d84e 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -30,11 +30,6 @@ import android.util.Log; import android.webkit.MimeTypeMap; import android.widget.Toast; -import com.owncloud.android.lib.common.OwnCloudBasicCredentials; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.OwnCloudClientFactory; -import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; -import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.resources.files.FileUtils; import java.io.File; @@ -182,33 +177,6 @@ public abstract class CommonUtils { return ContentResolver.getSyncAutomatically(account, METERED_NETWORK_ALLOWED_AUTHORITY); } - /** - * @param context app context - * @return Owncloud client instance or null - */ - public static OwnCloudClient getOwnCloudClient(Account account, Context context) { - Log.i(TAG, "getOwnCloudClient()"); - Uri serverUri; - OwnCloudClient oc; - try { - serverUri = Uri.parse(AccountUtils.getBaseUrlForAccount(context, account)); - oc = OwnCloudClientFactory.createOwnCloudClient(serverUri, context, true); - oc.setCredentials(new OwnCloudBasicCredentials(account.name, AccountManager.get(context).getPassword(account))); - - Log.d(TAG, "user agent: " + AppConstants.USER_AGENT); - if (!AppConstants.USER_AGENT.equals(OwnCloudClientManagerFactory.getUserAgent())) { - OwnCloudClientManagerFactory.setUserAgent(AppConstants.USER_AGENT); - } - - } catch (Exception e) { - Log.e(TAG, "Can\'t parse serverPath to Uri : " + e.toString()); - oc = null; - } - return oc; - - } - - /** methods relative to file **/ /** @@ -398,6 +366,13 @@ public abstract class CommonUtils { .setRequiresBatteryNotLow(true) .build(); + final OneTimeWorkRequest getUserInfoRequest = new OneTimeWorkRequest.Builder(AccountUserInfoWorker.class) + .setBackoffCriteria(BackoffPolicy.LINEAR, 2, TimeUnit.MINUTES) + .addTag(AppConstants.WORK_GENERIC_TAG) + .addTag(AppConstants.WORK_INITIALIZATION_TAG) + .setConstraints(constraints) + .build(); + final List workRequests = new ArrayList<>(); for (SyncedFolder folder : syncedFolders) { @@ -419,7 +394,8 @@ public abstract class CommonUtils { .addTag(AppConstants.WORK_INITIALIZATION_TAG) .build(); - workManager.beginWith(workRequests) + workManager.beginWith(getUserInfoRequest) + .then(workRequests) .then(firstStartRequest) .enqueue(); } diff --git a/app/src/main/java/foundation/e/drive/utils/DavClientProvider.java b/app/src/main/java/foundation/e/drive/utils/DavClientProvider.java index 02cb9142a0fab0e1035e8c5862cab7d1fd851ed5..4ca2ce5d70eefcef0add266948e3b13b0e44374d 100644 --- a/app/src/main/java/foundation/e/drive/utils/DavClientProvider.java +++ b/app/src/main/java/foundation/e/drive/utils/DavClientProvider.java @@ -10,24 +10,78 @@ package foundation.e.drive.utils; import android.accounts.Account; +import android.accounts.AccountManager; import android.content.Context; +import android.net.Uri; +import android.util.Log; +import com.nextcloud.common.NextcloudClient; +import com.nextcloud.common.OkHttpCredentialsUtil; +import com.owncloud.android.lib.common.OwnCloudBasicCredentials; import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.OwnCloudClientFactory; +import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; +import com.owncloud.android.lib.common.accounts.AccountUtils; /** * @author Vincent Bourgmayer */ public class DavClientProvider { - + private static final String TAG = DavClientProvider.class.getSimpleName(); private DavClientProvider (){} - private OwnCloudClient clientInstance; + @Deprecated + private OwnCloudClient ocClientInstance; + private NextcloudClient ncClientInstance; + + public OwnCloudClient getClientInstance(final Account account, final Context ctx) { + if (ocClientInstance == null) { + try { + //Not sure below if code is used... + if (!AppConstants.USER_AGENT.equals(OwnCloudClientManagerFactory.getUserAgent())) { + OwnCloudClientManagerFactory.setUserAgent(AppConstants.USER_AGENT); + } + final Uri serverUri = Uri.parse(AccountUtils.getBaseUrlForAccount(ctx, account)); + ocClientInstance = OwnCloudClientFactory.createOwnCloudClient(serverUri, ctx, true); + final String pwd = getAcountPwd(account, ctx); + Log.d(TAG, "name: "+account.name+"\n"+AccountManager.get(ctx).getUserData(account, AccountUtils.Constants.KEY_USER_ID)); + + ocClientInstance.setCredentials(new OwnCloudBasicCredentials(account.name, pwd)); + } catch (AccountUtils.AccountNotFoundException e) { + Log.e(TAG, "Can't parse serverPath to Uri : " + e.toString()); + return null; + } + } - public OwnCloudClient getClientInstance(Account account, Context ctx) { - if(clientInstance == null){ - this.clientInstance = CommonUtils.getOwnCloudClient(account, ctx); + if (ocClientInstance.getUserId() == null ) { + final String userId = AccountManager.get(ctx).getUserData(account, AppConstants.ACCOUNT_USER_ID_KEY); + ocClientInstance.setUserId(userId); } - return clientInstance; + + return ocClientInstance; + } + + + public NextcloudClient getNcClientInstance(final Account account, final Context ctx) { + Log.i(TAG, "getNcClientInstance()"); + if (ncClientInstance == null) { + try { + final Uri serverUri = Uri.parse(AccountUtils.getBaseUrlForAccount(ctx, account)); + final String credentials = OkHttpCredentialsUtil.basic(account.name, getAcountPwd(account, ctx)); + + ncClientInstance = OwnCloudClientFactory.createNextcloudClient(serverUri, account.name, credentials, ctx, true); + } catch (AccountUtils.AccountNotFoundException e) { + Log.e(TAG, "Can't get server URI for account: "+account.name+"\n"+e.getMessage()); + return null; + } + ncClientInstance.setUserId(account.name); + } + return ncClientInstance; + } + + + private static String getAcountPwd(Account account, Context ctx) throws AccountUtils.AccountNotFoundException { + return AccountManager.get(ctx).getPassword(account); } /** Holder */ diff --git a/app/src/main/java/foundation/e/drive/work/AccountUserInfoWorker.java b/app/src/main/java/foundation/e/drive/work/AccountUserInfoWorker.java index 741e8938107a0fc0b0261e8c8d24c05fd6888714..d027aaac2c2ee5a116a603890e3bfa13e839fbdb 100644 --- a/app/src/main/java/foundation/e/drive/work/AccountUserInfoWorker.java +++ b/app/src/main/java/foundation/e/drive/work/AccountUserInfoWorker.java @@ -14,6 +14,7 @@ import static foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_NAME; import static foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_RELATIVE_QUOTA_KEY; import static foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_TOTAL_QUOTA_KEY; import static foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_USED_QUOTA_KEY; +import static foundation.e.drive.utils.AppConstants.ACCOUNT_USER_ID_KEY; import android.accounts.Account; import android.accounts.AccountManager; @@ -22,6 +23,7 @@ import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.util.Log; import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; @@ -30,10 +32,12 @@ import androidx.work.WorkerParameters; import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.nextcloud.common.NextcloudClient; import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.Quota; import com.owncloud.android.lib.common.UserInfo; import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.resources.users.GetRemoteUserInfoOperation; +import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation; import java.util.ArrayList; @@ -42,6 +46,7 @@ import foundation.e.drive.activity.AccountsActivity; import foundation.e.drive.operations.GetAliasOperation; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DavClientProvider; import foundation.e.drive.widgets.EDriveWidget; /** @@ -49,12 +54,14 @@ import foundation.e.drive.widgets.EDriveWidget; * @author TheScarastic */ public class AccountUserInfoWorker extends Worker { + private static final String TAG = AccountUserInfoWorker.class.getSimpleName(); public static final String UNIQUE_WORK_NAME = "AccountUserInfoWorker"; - final AccountManager accountManager; - final GetRemoteUserInfoOperation getRemoteUserInfoOperation = new GetRemoteUserInfoOperation(); + private final AccountManager accountManager; + private final GetUserInfoRemoteOperation GetUserInfoRemoteOperation = new GetUserInfoRemoteOperation(); + private final Context mContext; private Account account; - private String id; + private String userId; public AccountUserInfoWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); @@ -67,13 +74,12 @@ public class AccountUserInfoWorker extends Worker { public Result doWork() { account = CommonUtils.getAccount(mContext.getString(R.string.eelo_account_type), accountManager); - OwnCloudClient client = CommonUtils.getOwnCloudClient(account, mContext); - - if (account != null && client != null) { - if (fetchUserInfo(client) && fetchAliases(client)) { + final NextcloudClient client = DavClientProvider.getInstance().getNcClientInstance(account, mContext); + if (client != null) { + if (fetchUserInfo(client) && fetchAliases()) { Glide.with(mContext) .load(client.getBaseUri() + AccountsActivity.NON_OFFICIAL_AVATAR_PATH - + client.getCredentials().getUsername() + "/" + 300) + + client.getUserId() + "/" + 300) .diskCacheStrategy(DiskCacheStrategy.ALL) .preload(); updateWidget(mContext); @@ -85,38 +91,37 @@ public class AccountUserInfoWorker extends Worker { return Result.failure(); } - private boolean fetchUserInfo(final OwnCloudClient client) { - final RemoteOperationResult ocsResult = getRemoteUserInfoOperation.execute(client); + private boolean fetchUserInfo(final NextcloudClient client) { + final RemoteOperationResult ocsResult = GetUserInfoRemoteOperation.execute(client); - if (ocsResult.isSuccess() && ocsResult.getData() != null && !ocsResult.getData().isEmpty()) { - final UserInfo userInfo = (UserInfo) ocsResult.getData().get(0); + if (ocsResult.isSuccess()) { + final UserInfo userInfo = ocsResult.getResultData(); - id = userInfo.id; - final String name; - if (userInfo.displayName == null || userInfo.displayName.isEmpty()) { - name = userInfo.alternateDisplayName; - } else { - name = userInfo.displayName; + if (accountManager.getUserData(account, ACCOUNT_USER_ID_KEY) == null) { + userId = userInfo.getId(); + client.setUserId(userId); + AccountManager.get(mContext).setUserData(account, ACCOUNT_USER_ID_KEY, userId); + Log.v(TAG, "UserId "+userId+" saved for account"); } - final double relativeQuota = userInfo.getQuota().relative; - long totalQuota = userInfo.getQuota().total; + final Quota userQuota = userInfo.getQuota(); + final double relativeQuota = userQuota.getRelative(); + long totalQuota = userQuota.getTotal(); if (totalQuota <= 0) { totalQuota = 0; } - final long usedQuota = userInfo.getQuota().used; - final String groups = String.join(",", userInfo.groups); - final String email = userInfo.email; - - accountManager.setUserData(account, ACCOUNT_DATA_NAME, name); - accountManager.setUserData(account, ACCOUNT_DATA_EMAIL, email); + final String groups = String.join(",", userInfo.getGroups()); + accountManager.setUserData(account, ACCOUNT_DATA_NAME, userInfo.getDisplayName()); + accountManager.setUserData(account, ACCOUNT_DATA_EMAIL, userInfo.getEmail()); accountManager.setUserData(account, ACCOUNT_DATA_GROUPS, groups); accountManager.setUserData(account, ACCOUNT_DATA_TOTAL_QUOTA_KEY, "" + totalQuota); accountManager.setUserData(account, ACCOUNT_DATA_RELATIVE_QUOTA_KEY, "" + relativeQuota); - accountManager.setUserData(account, ACCOUNT_DATA_USED_QUOTA_KEY, "" + usedQuota); + accountManager.setUserData(account, ACCOUNT_DATA_USED_QUOTA_KEY, "" + userQuota.getUsed()); addNotifAboutQuota(relativeQuota); + Log.d(TAG+"fetchUserInfo()", "Success"); return true; } + Log.d(TAG+"fetchUserInfo()", "Failure"); return false; } @@ -136,7 +141,7 @@ public class AccountUserInfoWorker extends Worker { } } - private boolean needToSendNotification(Context context) { + private boolean needToSendNotification(final Context context) { final String LAST_NOTIFICATION_TIMESTAMP_KEY = "last_notification_timestamp"; final long MILLI_SECONDS_IN_A_DAY = 24 * 60 * 60 * 1000; @@ -156,7 +161,6 @@ public class AccountUserInfoWorker extends Worker { .apply(); return true; } - return false; } @@ -179,18 +183,20 @@ public class AccountUserInfoWorker extends Worker { manager.notify(0, builder.build()); } - private boolean fetchAliases(final OwnCloudClient client) { - final GetAliasOperation getAliasOperation = new GetAliasOperation(id); - final RemoteOperationResult ocsResult = getAliasOperation.execute(client); - String aliases = ""; - if (ocsResult.isSuccess() && ocsResult.getData() != null && !ocsResult.getData().isEmpty()) { - ArrayList alias = new ArrayList<>(ocsResult.getData().size()); - for (Object object : ocsResult.getData()) { - alias.add(object.toString()); + private boolean fetchAliases() { + final OwnCloudClient ocClient = DavClientProvider.getInstance().getClientInstance(account, mContext); + final GetAliasOperation getAliasOperation = new GetAliasOperation(userId); + final RemoteOperationResult> ocsResult = getAliasOperation.execute(ocClient); + String aliases = ""; + + if (ocsResult.isSuccess()) { + final ArrayList aliasList = ocsResult.getResultData(); + if (aliasList != null && !aliasList.isEmpty()) { + aliases = String.join(",", aliasList); } - aliases = String.join(",", alias); } accountManager.setUserData(account, ACCOUNT_DATA_ALIAS_KEY, aliases); + Log.d(TAG+"fetchAliases()", "Success"); return true; } diff --git a/app/src/main/java/foundation/e/drive/work/CreateRemoteFolderWorker.java b/app/src/main/java/foundation/e/drive/work/CreateRemoteFolderWorker.java index d7c27ba3cbad7f05045978ce4aa051f3c3e5c7b5..2d995c642d3bbfb15c4c266abc659aaea804ea0b 100644 --- a/app/src/main/java/foundation/e/drive/work/CreateRemoteFolderWorker.java +++ b/app/src/main/java/foundation/e/drive/work/CreateRemoteFolderWorker.java @@ -19,6 +19,7 @@ import androidx.work.Data; import androidx.work.Worker; import androidx.work.WorkerParameters; +//import com.nextcloud.common.NextcloudClient; //not yet supported import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation; @@ -29,8 +30,10 @@ import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DavClientProvider; /** + * /!\ Doesn't require NextcloudClient yet * Create folder on ecloud for a given local folder * @author Vincent Bourgmayer */ @@ -74,8 +77,9 @@ public class CreateRemoteFolderWorker extends Worker { folder.mkdirs(); syncedFolder.setLastModified(folder.lastModified()); } - - final OwnCloudClient client = CommonUtils.getOwnCloudClient(account, context); + //Not yet supported + //final NextcloudClient client = DavClientProvider.getInstance().getNcClientInstance(account, context); + final OwnCloudClient client = DavClientProvider.getInstance().getClientInstance(account, context); if (client == null) { Log.e(TAG, "Can't get OwnCloudClient."); return Result.retry(); @@ -84,23 +88,20 @@ public class CreateRemoteFolderWorker extends Worker { final CreateFolderRemoteOperation mkcolRequest = new CreateFolderRemoteOperation(syncedFolder.getRemoteFolder(), true); - try { - final RemoteOperationResult result = mkcolRequest.execute(client, true); - if (result.isSuccess() || result.getCode() == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS) { - if(DbHelper.insertSyncedFolder(syncedFolder, context) >= 0 ) { - Log.d(TAG, "Insertion in DB succeed"); - } - return Result.success(); + final RemoteOperationResult result = mkcolRequest.execute(client); + if (result.isSuccess() || result.getCode() == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS) { + if(DbHelper.insertSyncedFolder(syncedFolder, context) >= 0 ) { + Log.d(TAG, "Insertion in DB succeed"); } - } catch (Exception e) { - Log.e(TAG, "Exception: "+e.getClass().getSimpleName()); + return Result.success(); } + return Result.retry(); } private SyncedFolder getSyncedFolderFromData() { - Data data = getInputData(); - SyncedFolder result = new SyncedFolder( + final Data data = getInputData(); + final SyncedFolder result = new SyncedFolder( data.getString(DATA_KEY_LIBELLE), data.getString(DATA_KEY_LOCAL_PATH), data.getString(DATA_KEY_REMOTE_PATH), @@ -118,7 +119,7 @@ public class CreateRemoteFolderWorker extends Worker { private Account getAccount() { - SharedPreferences prefs = getApplicationContext().getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + final SharedPreferences prefs = getApplicationContext().getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); final String accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, ""); final String accountType = prefs.getString(AccountManager.KEY_ACCOUNT_TYPE, ""); diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index cf03e547329a0abdb00f8deaa3b528a9594bc823..6e670f3cb624353ef75cca289fe79432ade7ab54 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -20,10 +20,7 @@ Anpassen Synchronisiere deine Fotos und \n Videos sicher mit der Cloud - Keine Aliase! - eDrive-Benachrichtigungskanal - Die Höchstquote des /e/-Kontos wurde erreicht - Die Höchstquote des /e/-Kontos ist fast erreicht + eDrive Benachrichtigungskanal Es ist nicht möglich, Dateien hochzuladen, die größer sind als der verbleibende Platz im Cloud-Speicher. Bitte unternehmen Sie etwas. 99 % der Höchstquote des Cloud-Speichers erreicht. Bitte unternimm etwas. Alias diff --git a/app/src/test/java/foundation/e/drive/TestUtils.java b/app/src/test/java/foundation/e/drive/TestUtils.java index 542a9d39cc1c2caed6813c50dd2ca64ef97cedbf..1da09d5d35710b3560276f3cff40765546aeb66c 100644 --- a/app/src/test/java/foundation/e/drive/TestUtils.java +++ b/app/src/test/java/foundation/e/drive/TestUtils.java @@ -3,15 +3,16 @@ package foundation.e.drive; import android.accounts.Account; import android.accounts.AccountManager; import android.content.Context; +import android.net.Uri; import android.os.Bundle; import android.util.Log; -import com.owncloud.android.lib.common.OwnCloudClient; +import com.nextcloud.common.NextcloudClient; import com.owncloud.android.lib.common.network.CertificateCombinedException; import com.owncloud.android.lib.common.network.NetworkUtils; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.status.GetRemoteStatusOperation; +import com.owncloud.android.lib.resources.users.GetStatusRemoteOperation; import java.io.File; import java.io.FileWriter; @@ -62,6 +63,12 @@ public abstract class TestUtils { return validAccount; } + + public static NextcloudClient getNcClient(Context context) { + final Uri serverUri = Uri.parse(TEST_SERVER_URI); + return new NextcloudClient(serverUri, TEST_ACCOUNT_NAME, TEST_ACCOUNT_PASSWORD, context); + } + /** * register in accountManager an account with name, password, type and server url provided at build */ @@ -84,13 +91,13 @@ public abstract class TestUtils { * @throws IOException exception * @throws InterruptedException exception */ - public static void testConnection(final OwnCloudClient client, final Context context) throws KeyStoreException, + public static void testConnection(final NextcloudClient client, final Context context) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, InterruptedException{ - GetRemoteStatusOperation getStatus = new GetRemoteStatusOperation(context); + GetStatusRemoteOperation getStatus = new GetStatusRemoteOperation(); RemoteOperationResult result = getStatus.execute(client); @@ -107,7 +114,7 @@ public abstract class TestUtils { Thread.sleep(1000); // retry - getStatus = new GetRemoteStatusOperation(context); + getStatus = new GetStatusRemoteOperation(); result = getStatus.execute(client); if (!result.isSuccess()) { diff --git a/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java b/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java index 698d06be9caebaf70d0d6625e2ab3bc68d9d7089..2ba3e269162d81417df1d3c48c747eee32e78e38 100644 --- a/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java +++ b/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java @@ -7,23 +7,25 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.accounts.Account; import android.accounts.AccountManager; import android.content.Context; import android.os.Build; +import androidx.test.core.app.ApplicationProvider; + +import com.nextcloud.common.NextcloudClient; import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.Quota; import com.owncloud.android.lib.common.UserInfo; import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.resources.users.GetRemoteUserInfoOperation; -import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowLog; @@ -37,6 +39,7 @@ import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.TestUtils; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DavClientProvider; @RunWith(RobolectricTestRunner.class) @@ -44,31 +47,35 @@ import foundation.e.drive.utils.CommonUtils; public class UploadFileOperationTest { private List syncedFileStates= new ArrayList<>(); - private final OwnCloudClient client; + private final OwnCloudClient ocClient; + private final NextcloudClient ncClient; private final AccountManager accountManager; private final Context context; private long userFreeQuota; + private long userTotalQuota; + private long userUsedQuota; + private double userRelativeQuota; + private final Account account; public UploadFileOperationTest() { - context = RuntimeEnvironment.getApplication(); + context = ApplicationProvider.getApplicationContext(); accountManager = AccountManager.get(context); ShadowLog.stream = System.out; TestUtils.loadServerCredentials(); TestUtils.prepareValidAccount(accountManager); - client = CommonUtils.getOwnCloudClient(CommonUtils.getAccount(TestUtils.TEST_ACCOUNT_NAME, TestUtils.TEST_ACCOUNT_TYPE, accountManager), context); - - try { - TestUtils.testConnection(client, context); - } catch (Exception e) { - System.out.println("test connection failed: "+e.getMessage()); - } - userFreeQuota = getUserRemoteFreeQuota(); + account = TestUtils.getValidAccount(); + ocClient = DavClientProvider.getInstance().getClientInstance(account, context); + ncClient = TestUtils.getNcClient(context); + userFreeQuota = 50; + userTotalQuota = 100; + userUsedQuota = 50; + userRelativeQuota = 50.0; } @Before public void setUp() { prepareDB(); //Create DB - assertNotNull("Client is null. unexpected!", client); + assertNotNull("Client is null. unexpected!", ocClient); } /** @@ -99,10 +106,11 @@ public class UploadFileOperationTest { /** * Create a local small file to use for upload test */ - private void createSmallFile() { + private File createSmallFile() { final String smallDummyFilePath = TestUtils.TEST_LOCAL_ROOT_FOLDER_PATH+"small/dummy.txt"; + File file = null; try { - TestUtils.createFile(smallDummyFilePath, 2); + file = TestUtils.createFile(smallDummyFilePath, 2); } catch (IOException | SecurityException e ) { fail(e.getMessage()); } @@ -111,6 +119,7 @@ public class UploadFileOperationTest { sfs.setId(DbHelper.manageSyncedFileStateDB(sfs, "INSERT", context)); syncedFileStates.add(sfs); + return file; } /** @@ -123,8 +132,9 @@ public class UploadFileOperationTest { if (!syncedFileStates.isEmpty()) { final SyncedFileState sfs = syncedFileStates.get(0); if (sfs != null) { - final RemoveFileOperation removeRemoteFileOp = new RemoveFileOperation(sfs); - removeRemoteFileOp.execute(client); + //final RemoveFileOperation removeRemoteFileOp = new RemoveFileOperation(sfs); + //removeRemoteFileOp.execute(ocClient); + syncedFileStates.remove(sfs); } } @@ -135,36 +145,24 @@ public class UploadFileOperationTest { } else return true; } - private long getUserRemoteFreeQuota() { - final GetRemoteUserInfoOperation getRemoteUserInfoOperation = new GetRemoteUserInfoOperation(); - final RemoteOperationResult ocsResult = getRemoteUserInfoOperation.execute(client); - - if (ocsResult.isSuccess() && ocsResult.getData() != null) { - UserInfo userInfo = (UserInfo) ocsResult.getData().get(0); - System.out.println("User free Quotas: "+userInfo.getQuota().getFree()); - System.out.println("User Total Quotas: "+userInfo.getQuota().getTotal()); - - return userInfo.getQuota().getFree(); - } - return -1; - } - /** * test upload of a file and check that it's ok */ @Test + //@Ignore("Mocking file uploading doesn't work, and I don't find why; But this isn't related to method under test") public void uploadNewSmallFile_shouldwork() { //tearDown removeSmallFile(); //clean the environnement - createSmallFile(); //preparation + final File file = createSmallFile(); //preparation final SyncedFileState sfs_fromDB = DbHelper.loadSyncedFile(context, syncedFileStates.get(0).getLocalPath(), true); assertTrue("SyncedFileState loaded from DB must have an empty Etag", sfs_fromDB.getLastETAG().isEmpty()); - boolean checkEtag = false; - UploadFileOperation testOperation = new UploadFileOperation(syncedFileStates.get(0), context); + final UploadFileOperation operation = Mockito.spy(new UploadFileOperation(syncedFileStates.get(0), account, context)); + mockUserInfoReading(operation); + mockSuccessfulFileUpload(operation, file); - final RemoteOperationResult result = testOperation.execute(client); + final RemoteOperationResult result = operation.execute(ocClient); String errorMsg = "The upload failed:\n http code: "+result.getHttpCode() +"\n, is success ?"+result.isSuccess() +"\n, log msg: "+result.getLogMessage() @@ -192,9 +190,9 @@ public class UploadFileOperationTest { SyncedFileState syncedFileState = null; //Test fails at the moment because of UploadFileOperation's constructor not checking for syncedFileState is null) // check https://gitlab.e.foundation/e/apps/eDrive/-/issues/120 - final UploadFileOperation testOperation = new UploadFileOperation(syncedFileState, context); + final UploadFileOperation testOperation = new UploadFileOperation(syncedFileState, account, context); - RemoteOperationResult result = testOperation.execute(client); + RemoteOperationResult result = testOperation.execute(ocClient); assertEquals("Expected result code was FORBIDDEN but got: "+result.getCode().name(), RemoteOperationResult.ResultCode.FORBIDDEN, result.getCode()); } @@ -212,12 +210,12 @@ public class UploadFileOperationTest { assertTrue("SyncedFileState loaded from DB must have an empty Etag", sfs_fromDB.getLastETAG().isEmpty()); boolean checkEtag = false; - UploadFileOperation testOperation = new UploadFileOperation(syncedFileStates.get(0), context); + UploadFileOperation testOperation = new UploadFileOperation(syncedFileStates.get(0), account, context); final File smallFile = new File(sfs_fromDB.getLocalPath()); assertTrue("Local file deletion return false instead of true", smallFile.delete()); - final RemoteOperationResult result = testOperation.execute(client); + final RemoteOperationResult result = testOperation.execute(ocClient); assertEquals("Expected result code was FORBIDDEN but got: "+result.getCode().name(), RemoteOperationResult.ResultCode.FORBIDDEN, result.getCode()); } @@ -230,13 +228,14 @@ public class UploadFileOperationTest { //long freeQuota = getUserRemoteFreeQuota(); assertFalse("Reading remote free quota fails"+userFreeQuota, -1 == userFreeQuota); //We don't care of parameter of UploadFileOperation's constructor - final RemoteOperationResult actualResult = new UploadFileOperation(Mockito.mock(SyncedFileState.class), context) - .checkAvailableSpace(client, (userFreeQuota+1)); + final UploadFileOperation operation = Mockito.spy(new UploadFileOperation(Mockito.mock(SyncedFileState.class), account, context)); + mockUserInfoReading(operation); + final RemoteOperationResult actualResult = operation.checkAvailableSpace(ncClient, (userFreeQuota+1)); assertEquals("Quota check ("+ userFreeQuota+"vs"+(userFreeQuota+1)+") failed", RemoteOperationResult.ResultCode.QUOTA_EXCEEDED, actualResult.getCode()); } /** - * Assert that uploading a file which size is exactly the amount of free quota isn't allowed + * Assert that uploading a file which size is exactlyF the amount of free quota isn't allowed */ @Test public void fileSizeEqualToFreeQuota_shouldBeAllowed() { @@ -244,8 +243,10 @@ public class UploadFileOperationTest { //long freeQuota = getUserRemoteFreeQuota(); assertFalse("Reading remote free quota fails"+userFreeQuota, -1 == userFreeQuota); - final RemoteOperationResult actualResult = new UploadFileOperation(Mockito.mock(SyncedFileState.class), context) - .checkAvailableSpace(client, userFreeQuota); + final UploadFileOperation operation = Mockito.spy(new UploadFileOperation(Mockito.mock(SyncedFileState.class), account, context)); + mockUserInfoReading(operation); + + final RemoteOperationResult actualResult = operation.checkAvailableSpace(ncClient, userFreeQuota); assertNotEquals("Quota check is Quota Exceeded ("+ userFreeQuota+" vs "+userFreeQuota+")", RemoteOperationResult.ResultCode.QUOTA_EXCEEDED, actualResult.getCode()); @@ -264,10 +265,27 @@ public class UploadFileOperationTest { //long freeQuota = getUserRemoteFreeQuota(); assertFalse("Reading remote free quota fails "+userFreeQuota, -1 == userFreeQuota); - final RemoteOperationResult actualResult = new UploadFileOperation(Mockito.mock(SyncedFileState.class), context) - .checkAvailableSpace(client, (userFreeQuota-1)); + final UploadFileOperation operation = Mockito.spy(new UploadFileOperation(Mockito.mock(SyncedFileState.class), account, context)); + mockUserInfoReading(operation); + final RemoteOperationResult actualResult = operation.checkAvailableSpace(ncClient, (userFreeQuota-1)); assertEquals("Quota check ("+ userFreeQuota+" vs "+(userFreeQuota-1)+") failed", RemoteOperationResult.ResultCode.OK, actualResult.getCode()); } + + + private void mockUserInfoReading(UploadFileOperation instanceUnderTest) { + final Quota quota = new Quota(userFreeQuota, userUsedQuota, userTotalQuota, userRelativeQuota, 100); + final UserInfo uInfo = new UserInfo("id", true, "toto", "toto@toto.com", "+123456789", "adress", "", "", quota, null); + final RemoteOperationResult mockResponse = new RemoteOperationResult(RemoteOperationResult.ResultCode.OK); + mockResponse.setResultData(uInfo); + Mockito.doReturn(mockResponse).when(instanceUnderTest).readUserInfo(ncClient); + } + + private void mockSuccessfulFileUpload(final UploadFileOperation instanceUnderTest, final File file) { + final String eTag = "123456789"; + final RemoteOperationResult mockResponse = new RemoteOperationResult(RemoteOperationResult.ResultCode.OK); + mockResponse.setResultData(eTag); + Mockito.doReturn(mockResponse).when(instanceUnderTest).uploadFile(file, ocClient); + } } \ No newline at end of file diff --git a/app/src/test/java/foundation/e/drive/services/AbstractServiceIT.java b/app/src/test/java/foundation/e/drive/services/AbstractServiceIT.java index a1631078f95be8311f2a5ea10320f543006ad2ad..314e1141c4b982e3a25cec894c5b3608cf1e10c3 100644 --- a/app/src/test/java/foundation/e/drive/services/AbstractServiceIT.java +++ b/app/src/test/java/foundation/e/drive/services/AbstractServiceIT.java @@ -25,6 +25,8 @@ import static foundation.e.drive.TestUtils.TEST_ACCOUNT_TYPE; import static foundation.e.drive.utils.AppConstants.MEDIASYNC_PROVIDER_AUTHORITY; import static foundation.e.drive.utils.AppConstants.SETTINGSYNC_PROVIDER_AUTHORITY; +import com.nextcloud.common.NextcloudClient; + @RunWith(RobolectricTestRunner.class) @Config(sdk = Build.VERSION_CODES.O, manifest = Config.NONE) public abstract class AbstractServiceIT { @@ -45,7 +47,7 @@ public abstract class AbstractServiceIT { protected ConnectivityManager connectivityManager; protected JobScheduler jobScheduler; protected DbHelper dbHelper; - + protected NextcloudClient client; protected int initial_folder_number=0; //number of folders to sync at initialization protected long last_sync_time=0l; //Timestamp of the end of the last synchronisation protected boolean init_done = true; //true if InitializerService did its job diff --git a/app/src/test/java/foundation/e/drive/services/InitializerServiceTest.java b/app/src/test/java/foundation/e/drive/services/InitializerServiceTest.java index 8d5bfb3da093ec211c3f89c903fb40b8ba18c3fc..725d8bd8d080d4297568813adaecf2db5dd36099 100644 --- a/app/src/test/java/foundation/e/drive/services/InitializerServiceTest.java +++ b/app/src/test/java/foundation/e/drive/services/InitializerServiceTest.java @@ -9,6 +9,7 @@ import android.content.Context; import android.net.ConnectivityManager; +import androidx.test.core.app.ApplicationProvider; import androidx.work.WorkManager; import org.junit.Test; @@ -26,7 +27,7 @@ public class InitializerServiceTest extends AbstractServiceIT { private final ServiceController syncServiceController; private final SynchronizationService syncService; + public ObserverServiceTest(){ syncServiceController = Robolectric.buildService(SynchronizationService.class); syncService = syncServiceController.get(); mServiceController = Robolectric.buildService(ObserverService.class); mService = mServiceController.get(); - context = RuntimeEnvironment.getApplication(); + context = ApplicationProvider.getApplicationContext(); accountManager = AccountManager.get(context); jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); contentResolver = context.getContentResolver(); sharedPreferences = context.getSharedPreferences( AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); dbHelper = new DbHelper(context); + client = TestUtils.getNcClient(context); } /** * Set the network status to an available wifi */ - private void setWifiNetworkStatus(){ + private void setWifiNetworkStatus() { NetworkInfo netInfo = ShadowNetworkInfo.newInstance(null, ConnectivityManager.TYPE_WIFI, 0, true, NetworkInfo.State.CONNECTED); assertEquals("NetworkInfo type is invalid",ConnectivityManager.TYPE_WIFI,netInfo.getType()); @@ -72,7 +78,7 @@ public class ObserverServiceTest extends AbstractServiceIT { /** * Create a network status where no connection is available */ - private void setUnavailableWifiNetworkStatus(){ + private void setUnavailableWifiNetworkStatus() { NetworkInfo netInfo = ShadowNetworkInfo.newInstance(null, ConnectivityManager.TYPE_WIFI, 0, true, NetworkInfo.State.DISCONNECTED); assertEquals("NetworkInfo type is invalid",ConnectivityManager.TYPE_WIFI,netInfo.getType()); @@ -84,7 +90,7 @@ public class ObserverServiceTest extends AbstractServiceIT { * Create a single 'SyncedFolder' instance for 'eDrive-test' folder * @return SyncedFolder instance */ - private SyncedFolder createSingleTestSyncedFolder(){ + private SyncedFolder createSingleTestSyncedFolder() { final File folder = new File(TEST_LOCAL_ROOT_FOLDER_PATH); try{ folder.mkdirs(); @@ -101,9 +107,7 @@ public class ObserverServiceTest extends AbstractServiceIT { * Send request to server to create the remote folder of the given syncedFolder * @param folder the local folder metadata to create */ - private void createRemoteFolder(SyncedFolder folder){ - final OwnCloudClient client = CommonUtils.getOwnCloudClient(getValidAccount(), context); - + private void createRemoteFolder(SyncedFolder folder) { try { TestUtils.testConnection(client, context); }catch(Exception e){ @@ -151,12 +155,10 @@ public class ObserverServiceTest extends AbstractServiceIT { */ @Ignore("Ignore until a way to prepare unavailable Network has been founded") @Test - public void noNetwork_shouldStop(){ - //setWifiNetworkStatus(); Make the test to fail as expected - //setUnavailableWifiNetworkStatus(); //Doesn't give the expected result at the moment so the test fails! + public void noNetwork_shouldStop() { prepareValidAccount(); enableMediaAndSettingsSync(getValidAccount()); - //createRemoteSyncedFolder(createSingleTestSyncedFolder()); + registerSharedPref(); boolean haveNetworkConnexion = CommonUtils.haveNetworkConnection(RuntimeEnvironment.application, true); @@ -177,12 +179,11 @@ public class ObserverServiceTest extends AbstractServiceIT { */ @Ignore("Binding to synchronizationService make test fails") @Test - public void lastSyncWasLessThan15minAgo_shouldStop(){ + public void lastSyncWasLessThan15minAgo_shouldStop() { last_sync_time = System.currentTimeMillis() - 899900; setWifiNetworkStatus(); prepareValidAccount(); enableMediaAndSettingsSync(getValidAccount()); - //createRemoteSyncedFolder(createSingleTestSyncedFolder()); registerSharedPref(); //Start the service @@ -203,13 +204,12 @@ public class ObserverServiceTest extends AbstractServiceIT { */ @Ignore("Binding to synchronizationService make test fails") @Test - public void lastSync15minAnd30secAgo_shouldStart(){ + public void lastSync15minAnd30secAgo_shouldStart() { //decrease 15min and 30sec last_sync_time = System.currentTimeMillis() - 930000; setWifiNetworkStatus(); prepareValidAccount(); enableMediaAndSettingsSync(getValidAccount()); - //createRemoteSyncedFolder(createSingleTestSyncedFolder()); registerSharedPref(); syncServiceController.create().startCommand(0, 0); @@ -227,7 +227,7 @@ public class ObserverServiceTest extends AbstractServiceIT { */ @Ignore("Not yet implemented") @Test - public void syncAlreadyStarted_shouldStop(){ + public void syncAlreadyStarted_shouldStop() { //@TODO need to find how to access the "isRunning" private field inside the ObserverService for this test fail("Not yet implemented "); } @@ -238,7 +238,7 @@ public class ObserverServiceTest extends AbstractServiceIT { */ @Ignore("Binding to synchronizationService make test fails") @Test - public void noAccount_shouldStop(){ + public void noAccount_shouldStop() { mServiceController.create().startCommand(0, 0); @@ -255,13 +255,12 @@ public class ObserverServiceTest extends AbstractServiceIT { */ @Ignore("Binding to synchronizationService make test fails") @Test - public void InitializationNotDone_shouldStop(){ + public void InitializationNotDone_shouldStop() { init_done = false; //This is the key settings for this test setWifiNetworkStatus(); prepareValidAccount(); enableMediaAndSettingsSync(getValidAccount()); - //createRemoteSyncedFolder(createSingleTestSyncedFolder()); registerSharedPref(); diff --git a/build.gradle b/build.gradle index 3378f0cd90a587b6f781e45f221ba00f97f51ebf..3858e8721ea3bb6fc4d91cb1cab840b43a985749 100644 --- a/build.gradle +++ b/build.gradle @@ -1,25 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - - repositories { - google() - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' - - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -allprojects { - repositories { - google() - jcenter() - } +plugins { + id 'com.android.application' version '7.1.3' apply false + id 'com.android.library' version '7.1.3' apply false } task clean(type: Delete) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 46b275d78fb233a28426185786ef96bde557b078..75a02d861565ea816279884314c95c1e83dbe849 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip diff --git a/nextcloud-android-lib b/nextcloud-android-lib deleted file mode 160000 index b043a5104962a841daf4ca3e59383216eebf7ef0..0000000000000000000000000000000000000000 --- a/nextcloud-android-lib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b043a5104962a841daf4ca3e59383216eebf7ef0 diff --git a/settings.gradle b/settings.gradle index 19a2fc3db4027e847aaf59bf0c8f3a6f924d4615..a18c791d2cb98b8f38cf4d8dc7ac37b1d7d6720d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,17 @@ -include ':app', ':NextcloudLib' -project(':NextcloudLib').projectDir = new File('nextcloud-android-lib') \ No newline at end of file +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { url "https://jitpack.io" } + } +} +rootProject.name = "eDrive" +include ':app' \ No newline at end of file