From 0a9420db827f65212cc1a3c8cf74d4116f3da7d3 Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Thu, 10 Mar 2022 17:03:12 +0000 Subject: [PATCH 01/25] Use WorkManager API to schedule periodic sync --- .gitlab-ci.yml | 2 +- app/build.gradle | 33 ++++----- app/src/main/AndroidManifest.xml | 11 --- .../foundation/e/drive/EdriveApplication.java | 10 +-- .../foundation/e/drive/jobs/ScannerJob.java | 54 -------------- .../drive/receivers/BatteryStateReceiver.java | 41 ----------- .../e/drive/receivers/ScreenOffReceiver.java | 51 ------------- .../e/drive/services/InitializerService.java | 71 ++++++++----------- .../e/drive/services/ObserverService.java | 2 - .../e/drive/services/ResetService.java | 12 ++-- .../e/drive/utils/AppConstants.java | 2 +- .../foundation/e/drive/utils/CommonUtils.java | 52 ++++++++------ .../foundation/e/drive/utils/JobUtils.java | 70 ------------------ .../e/drive/work/FullScanWorker.java | 63 ++++++++++++++++ 14 files changed, 144 insertions(+), 330 deletions(-) delete mode 100644 app/src/main/java/foundation/e/drive/jobs/ScannerJob.java delete mode 100644 app/src/main/java/foundation/e/drive/receivers/BatteryStateReceiver.java delete mode 100644 app/src/main/java/foundation/e/drive/receivers/ScreenOffReceiver.java delete mode 100644 app/src/main/java/foundation/e/drive/utils/JobUtils.java create mode 100644 app/src/main/java/foundation/e/drive/work/FullScanWorker.java diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d475c315..30859edd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "registry.gitlab.e.foundation:5000/e/apps/docker-android-apps-cicd:latest" +image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:latest" stages: - test diff --git a/app/build.gradle b/app/build.gradle index c65b01d7..018d91fd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,7 +23,7 @@ def getTestProp(String propName) { android { - compileSdkVersion 28 + compileSdkVersion 31 defaultConfig { applicationId "foundation.e.drive" minSdkVersion 26 @@ -53,32 +53,27 @@ android { //includeAndroidResources = true } } - } dependencies { + api project(':NextcloudLib') implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support:appcompat-v7:26.1.0' - - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test:rules:1.0.2' - androidTestImplementation 'androidx.annotation:annotation:1.3.0' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' api 'androidx.annotation:annotation:1.3.0' - api project(':NextcloudLib') - //start to add lib for test - 1/4/21 - //@TODO: add junit runner as lib for testImplementation + def work_version = "2.7.1" + // (Java only) + implementation "androidx.work:work-runtime:$work_version" - testImplementation 'com.android.support.test:runner:1.0.2' - testImplementation 'com.android.support.test:rules:1.0.2' + 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' + + testImplementation 'androidx.test:runner:1.4.0' + testImplementation 'androidx.test:rules:1.4.0' testImplementation 'junit:junit:4.12' - //testImplementation 'org.robolectric:robolectric:4.4' //need AndroidX - testImplementation "org.robolectric:robolectric:3.8" + testImplementation 'org.robolectric:robolectric:4.4' testImplementation('org.mockito:mockito-inline:3.4.0') - - //testImplementation Libs.AndroidX.Test.archCoreTesting //TODO: replace by not android X version - //implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' - androidTestImplementation 'junit:junit:4.12' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index be871995..7337a890 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -61,22 +61,11 @@ http://www.gnu.org/licenses/gpl.html - - - - - - - diff --git a/app/src/main/java/foundation/e/drive/EdriveApplication.java b/app/src/main/java/foundation/e/drive/EdriveApplication.java index de7bdcf7..b6f6ae35 100644 --- a/app/src/main/java/foundation/e/drive/EdriveApplication.java +++ b/app/src/main/java/foundation/e/drive/EdriveApplication.java @@ -17,7 +17,6 @@ import android.util.Log; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; -import foundation.e.drive.utils.JobUtils; /** * Class representing the eDrive application. @@ -36,7 +35,7 @@ public class EdriveApplication extends Application { SharedPreferences prefs = getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); if (prefs.getString(AccountManager.KEY_ACCOUNT_NAME, null) != null) { - scheduleScannerJob(); + Log.d(TAG, "Account already registered"); } else { Account mAccount = CommonUtils.getAccount(getString(R.string.eelo_account_type), AccountManager.get(this)); if (mAccount != null) { @@ -47,17 +46,10 @@ public class EdriveApplication extends Application { .putString(AccountManager.KEY_ACCOUNT_TYPE, accountType) .apply(); - scheduleScannerJob(); } } } - private void scheduleScannerJob() { - if (!JobUtils.isScannerJobRegistered(this)) { - JobUtils.scheduleScannerJob(this); - } - } - private void resetOperationManagerSetting() { getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE).edit() .putBoolean(AppConstants.KEY_OMS_IS_WORKING, false) diff --git a/app/src/main/java/foundation/e/drive/jobs/ScannerJob.java b/app/src/main/java/foundation/e/drive/jobs/ScannerJob.java deleted file mode 100644 index 878f554b..00000000 --- a/app/src/main/java/foundation/e/drive/jobs/ScannerJob.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright © Vincent Bourgmayer (/e/ foundation). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package foundation.e.drive.jobs; - -import android.app.job.JobParameters; -import android.app.job.JobService; -import android.content.Intent; -import android.content.IntentFilter; -import android.util.Log; -import foundation.e.drive.receivers.ScreenOffReceiver; -import foundation.e.drive.services.ObserverService; -import foundation.e.drive.utils.CommonUtils; - -/** - * @author Vincent Bourgmayer - */ -public class ScannerJob extends JobService { - final private String TAG = ScannerJob.class.getSimpleName(); //Tag for log - - @Override - public boolean onStartJob(JobParameters params) { - Log.i(TAG, "onStartJob()"); - - Log.d(TAG, "RegisterReceiver: screenOffReceiver"); - IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SCREEN_OFF); - getApplicationContext().registerReceiver(ScreenOffReceiver.getInstance(), filter); - - Intent observerServiceIntent = new Intent(this, ObserverService.class); - this.startService(observerServiceIntent); - jobFinished(params, false); - return true; - } - - /** - * - * @param params - * @return default return... ? - */ - @Override - public boolean onStopJob(JobParameters params) { - Log.i(TAG, "onStopJob"); - boolean unregisteredReceiver = CommonUtils.unregisterScreenOff(getApplicationContext()); - Intent observerServiceIntent = new Intent(this, ObserverService.class); - this.stopService(observerServiceIntent); - return false; - } -} diff --git a/app/src/main/java/foundation/e/drive/receivers/BatteryStateReceiver.java b/app/src/main/java/foundation/e/drive/receivers/BatteryStateReceiver.java deleted file mode 100644 index 2fd661d1..00000000 --- a/app/src/main/java/foundation/e/drive/receivers/BatteryStateReceiver.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright © Vincent Bourgmayer (/e/ foundation). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package foundation.e.drive.receivers; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import foundation.e.drive.utils.JobUtils; - -/** - * @author Vincent Bourgmayer - */ -public class BatteryStateReceiver extends BroadcastReceiver { - private final static String TAG = BatteryStateReceiver.class.getSimpleName(); - - @Override - public void onReceive(Context context, Intent intent) { - Log.i(TAG, "onReceive"); - - String intentAction = intent.getAction(); - if(intentAction == null) { - Log.e(TAG, "intent Action is null"); - } else if ( intentAction.equals(Intent.ACTION_BATTERY_OKAY) ) { - JobUtils.scheduleScannerJob(context); - }else if(intentAction.equals(Intent.ACTION_BATTERY_LOW)){ - JobUtils.stopScheduledJob(context, JobUtils.ScannerJobId); - try { - context.unregisterReceiver(ScreenOffReceiver.getInstance()); - }catch(Exception e){ - Log.e(TAG, e.toString() ); - } - } - } -} diff --git a/app/src/main/java/foundation/e/drive/receivers/ScreenOffReceiver.java b/app/src/main/java/foundation/e/drive/receivers/ScreenOffReceiver.java deleted file mode 100644 index 27d48959..00000000 --- a/app/src/main/java/foundation/e/drive/receivers/ScreenOffReceiver.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright © Vincent Bourgmayer (/e/ foundation). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package foundation.e.drive.receivers; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import foundation.e.drive.services.ObserverService; -import foundation.e.drive.utils.CommonUtils; - -/** - * @author Vincent Bourgmayer - * This is a broadcast receiver which catch "ACTION_SCREEN_OFF" to start scanning at a moment - * where the user won't need battery or network. - */ -public class ScreenOffReceiver extends BroadcastReceiver { - private final String TAG = ScreenOffReceiver.class.getSimpleName(); - private static ScreenOffReceiver instance; - - public static ScreenOffReceiver getInstance(){ - if(instance == null) - instance = new ScreenOffReceiver(); - return instance; - } - - /** - * Private constructor - */ - private ScreenOffReceiver(){} - - @Override - public void onReceive(Context context, Intent intent) { - Log.i(TAG, "onReceive"); - String intentAction = intent.getAction(); - if(intentAction == null){ - Log.e(TAG, "intent Action is null"); - } else if ( intent.getAction().equals(Intent.ACTION_SCREEN_OFF) - && CommonUtils.haveNetworkConnexion( context ) ) { - Log.d(TAG, "onReceive: ACTION_SCREEN_OFF"); - Intent cloudIntent = new Intent(context, ObserverService.class); - context.startService(cloudIntent); - } - } -} 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 289229b2..ad682250 100644 --- a/app/src/main/java/foundation/e/drive/services/InitializerService.java +++ b/app/src/main/java/foundation/e/drive/services/InitializerService.java @@ -13,7 +13,6 @@ import android.accounts.AccountManager; import android.app.Service; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.Build; import android.os.Environment; @@ -26,17 +25,16 @@ 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.Arrays; import java.util.List; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.operations.CreateInitialFolderRemoteOperation; -import foundation.e.drive.receivers.ScreenOffReceiver; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; -import foundation.e.drive.utils.JobUtils; -import foundation.e.drive.utils.ServiceExceptionHandler; + import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; import static foundation.e.drive.utils.AppConstants.INITIALFOLDERS_NUMBER; @@ -44,15 +42,16 @@ import static foundation.e.drive.utils.AppConstants.MEDIA_SYNCABLE_CATEGORIES; import static foundation.e.drive.utils.AppConstants.SETTINGS_SYNCABLE_CATEGORIES; import androidx.annotation.Nullable; +import androidx.work.WorkManager; /** * @author Vincent Bourgmayer */ public class InitializerService extends Service implements OnRemoteOperationListener { - final private String TAG = InitializerService.class.getSimpleName(); - //Complex properties - private int existingRemoteFolderCounter; //@dev-only; Temporarily used to know if all remotePath exist - private List mSyncedFolders; //syncedFolders + private final String TAG = InitializerService.class.getSimpleName(); + + private int existingRemoteFolderCounter; //Temporarily used to know if all remotePath exist + private List mSyncedFolders; private OwnCloudClient mCloudClient; private Handler mHandler; private Account mAccount; @@ -63,7 +62,6 @@ public class InitializerService extends Service implements OnRemoteOperationList Log.i(TAG, "onCreate()"); super.onCreate(); this.existingRemoteFolderCounter = 0; - //JobUtils.scheduleInitializerJob(getApplicationContext()); } @Override @@ -74,10 +72,11 @@ public class InitializerService extends Service implements OnRemoteOperationList //Get account SharedPreferences prefs = this.getSharedPreferences( AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE ); - if( prefs.getBoolean( AppConstants.INITIALIZATION_HAS_BEEN_DONE, false ) ) { - JobUtils.scheduleScannerJob(this); + + if (prefs.getBoolean( AppConstants.INITIALIZATION_HAS_BEEN_DONE, false ) ) { + Log.w(TAG, "Initializer has already been run"); - }else{ + } else { String accountName = prefs.getString( AccountManager.KEY_ACCOUNT_NAME, "" ); String accountType = prefs.getString( AccountManager.KEY_ACCOUNT_TYPE, "" ); @@ -86,23 +85,21 @@ public class InitializerService extends Service implements OnRemoteOperationList accountName = intent.getExtras().getString( AccountManager.KEY_ACCOUNT_NAME, "" ); accountType = intent.getExtras().getString( AccountManager.KEY_ACCOUNT_TYPE, "" ); - //If data come from intent, store them into pref because there aren't stored prefs.edit().putString( AccountManager.KEY_ACCOUNT_NAME, accountName ) .putString( AccountManager.KEY_ACCOUNT_TYPE, accountType ) .apply(); } - if(accountName.isEmpty() ) { + if (accountName.isEmpty() ) { Log.w(TAG, "Account's name not found. Neither in shared prefs nor in intent's extras"); - //JobUtils.stopScheduledJob(getApplicationContext(), JobUtils.InitializerJobId); stopSelf(); - }else{ + } else { this.mAccount = CommonUtils.getAccount( accountName, accountType, AccountManager.get(this) ); //Get OwnCloudlient if (this.mAccount != null) { this.mCloudClient = CommonUtils.getOwnCloudClient( this.mAccount, getApplicationContext()); start(); - }else { + } else { Log.w(TAG, "Got account is invalid."); stopSelf(); } @@ -123,7 +120,6 @@ public class InitializerService extends Service implements OnRemoteOperationList return; } - //Get categories of element to sync List syncCategories = new ArrayList<>(); if (CommonUtils.isMediaSyncEnabled(mAccount)) { @@ -134,7 +130,6 @@ public class InitializerService extends Service implements OnRemoteOperationList syncCategories.addAll(Arrays.asList(SETTINGS_SYNCABLE_CATEGORIES)); } - //Get SyncedFolders getInitialSyncedFolders(syncCategories); this.existingRemoteFolderCounter = 0; @@ -195,7 +190,7 @@ public class InitializerService extends Service implements OnRemoteOperationList false, true, false) ); - }catch (Exception e){ Log.e(TAG, e.toString()); } + } catch (Exception e){ Log.e(TAG, e.toString()); } break; } } @@ -212,15 +207,14 @@ public class InitializerService extends Service implements OnRemoteOperationList Log.i(TAG, "createNextRemoteFolder()"); this.restartFolderCreationCounter = 0; - if( this.mSyncedFolders == null || this.mSyncedFolders.isEmpty() ){ - //JobUtils.stopScheduledJob(getApplicationContext(), JobUtils.InitializerJobId); + if (this.mSyncedFolders == null || this.mSyncedFolders.isEmpty() ){ this.stopSelf(); } //It means that there are still folders to create - if( this.existingRemoteFolderCounter < this.mSyncedFolders.size() ){ + if (this.existingRemoteFolderCounter < this.mSyncedFolders.size() ){ - if( this.mHandler == null ) this.mHandler = new Handler(); + if (this.mHandler == null ) this.mHandler = new Handler(); CreateInitialFolderRemoteOperation createFolderOperation = new CreateInitialFolderRemoteOperation( @@ -230,11 +224,11 @@ public class InitializerService extends Service implements OnRemoteOperationList createFolderOperation.execute(this.mCloudClient, this, this.mHandler); - }else if(this.existingRemoteFolderCounter == this.mSyncedFolders.size() ){ + } else if (this.existingRemoteFolderCounter == this.mSyncedFolders.size() ){ doLastStep(); - }else{ + } else { Log.e(TAG, "this.existingRemoteFolderCounter : "+this.existingRemoteFolderCounter+" > this.mSyncedFolders.size() : "+this.mSyncedFolders.size() ); this.stopSelf(); } @@ -243,26 +237,26 @@ public class InitializerService extends Service implements OnRemoteOperationList @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { Log.i(TAG, "onRemoteOperationFinish()"); - if(operation instanceof CreateInitialFolderRemoteOperation){ + if (operation instanceof CreateInitialFolderRemoteOperation){ - if(result.isSuccess() || result.getCode() == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS){ + if (result.isSuccess() || result.getCode() == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS){ this.existingRemoteFolderCounter+=1; CreateNextRemoteFolder(); - }else if( result.getHttpCode() == 423 || result.getHttpCode() == 409){//file locked or conflict in result + } else if (result.getHttpCode() == 423 || result.getHttpCode() == 409){//file locked or conflict in result Log.e( TAG, result.getLogMessage() ); - if( this.restartFolderCreationCounter < 3) { + if (this.restartFolderCreationCounter < 3) { Log.w( TAG, " restart operation" ); operation.execute( this.mCloudClient, this, this.mHandler ); this.restartFolderCreationCounter+=1; - }else{ + } else { Log.e(TAG, "Remote folder's creation failed due to conflict with server"); stopSelf(); } - }else{ + } else { Log.e(TAG, result.getLogMessage()+" "+result.getHttpCode() ); stopSelf(); } @@ -275,8 +269,7 @@ public class InitializerService extends Service implements OnRemoteOperationList **/ private void doLastStep(){ Log.i(TAG, "doLastStep()"); - - Context appContext = getApplicationContext(); + final Context appContext = getApplicationContext(); appContext.getSharedPreferences( AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE ) .edit() @@ -284,22 +277,16 @@ public class InitializerService extends Service implements OnRemoteOperationList .putInt( INITIALFOLDERS_NUMBER, mSyncedFolders.size() ) .apply(); + CommonUtils.registerPeriodicFullScanWorker(WorkManager.getInstance(appContext)); + //all folder have been created - //JobUtils.stopScheduledJob(appContext, JobUtils.InitializerJobId); - JobUtils.scheduleScannerJob(appContext); - Log.d(TAG, "RegisterReceiver: screenOffReceiver"); - IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SCREEN_OFF); - getApplicationContext().registerReceiver(ScreenOffReceiver.getInstance(), filter); //Immediatly start ObserverService to not have to wait 30 minutes. Intent observersServiceIntent = new Intent(getApplicationContext(), foundation.e.drive.services.ObserverService.class); startService(observersServiceIntent); - stopSelf(); - } @Override 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 c90b4167..a0e8c7eb 100644 --- a/app/src/main/java/foundation/e/drive/services/ObserverService.java +++ b/app/src/main/java/foundation/e/drive/services/ObserverService.java @@ -49,7 +49,6 @@ import foundation.e.drive.receivers.ForceSyncReceiver; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; -import foundation.e.drive.utils.JobUtils; import foundation.e.drive.utils.ServiceExceptionHandler; import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; @@ -95,7 +94,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene // Check if account is invalid if(this.mAccount == null){ Log.w(TAG, "No account registered"); - JobUtils.stopScheduledJob(this, JobUtils.ScannerJobId); //If no account return super.onStartCommand(intent, flags, startId); } diff --git a/app/src/main/java/foundation/e/drive/services/ResetService.java b/app/src/main/java/foundation/e/drive/services/ResetService.java index dad3cb20..7b7c9d4e 100644 --- a/app/src/main/java/foundation/e/drive/services/ResetService.java +++ b/app/src/main/java/foundation/e/drive/services/ResetService.java @@ -21,12 +21,12 @@ import java.io.File; import foundation.e.drive.database.DbHelper; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; -import foundation.e.drive.utils.JobUtils; import static foundation.e.drive.utils.AppConstants.INITIALFOLDERS_NUMBER; import static foundation.e.drive.utils.AppConstants.INITIALIZATION_HAS_BEEN_DONE; import androidx.annotation.Nullable; +import androidx.work.WorkManager; /** * @author Vincent Bourgmayer @@ -64,9 +64,8 @@ public class ResetService extends Service { result = getApplicationContext().stopService( stopperIntent ); Log.d(TAG, "stop OperationManagerService: "+result); - //2. Stop the scheduledJob - JobUtils.stopScheduledJob(this, JobUtils.ScannerJobId); - //JobUtils.stopScheduledJob(this, JobUtils.InitializerJobId); + WorkManager.getInstance(this).cancelAllWorkByTag(AppConstants.WORK_GENERIC_TAG); + //3. delete DB result = this.deleteDatabase(DbHelper.DATABASE_NAME); Log.d(TAG, "Remove Database: "+result); @@ -86,11 +85,8 @@ public class ResetService extends Service { .apply(); } - //5. Unregister screenOffReceiver - result = CommonUtils.unregisterScreenOff(getApplicationContext()); - Log.d(TAG, "Unregistered ScreenOffReceiver: "+result); - //6. Remove Cached File + //5. Remove Cached File File[] cachedFiles = this.getApplicationContext().getExternalCacheDir().listFiles(); for(File f : cachedFiles){ f.delete(); 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 1e180424..7506a525 100644 --- a/app/src/main/java/foundation/e/drive/utils/AppConstants.java +++ b/app/src/main/java/foundation/e/drive/utils/AppConstants.java @@ -40,7 +40,7 @@ public abstract class AppConstants { public final static String notificationChannelID ="3310"; public final static String notificationChannelName="eDrive channel"; - + public final static String WORK_GENERIC_TAG="eDrive"; public final static String USER_AGENT = "eos("+getBuildTime()+")-eDrive("+ BuildConfig.VERSION_NAME +")"; 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 e0f762fc..21c2099b 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -17,7 +17,6 @@ import android.content.ContentResolver; import android.content.Context; import android.media.MediaScannerConnection; import android.net.ConnectivityManager; -import android.net.Network; import android.net.NetworkInfo; import android.net.Uri; import android.util.Log; @@ -31,13 +30,20 @@ import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.resources.files.FileUtils; import java.io.File; +import java.util.concurrent.TimeUnit; -import foundation.e.drive.receivers.ScreenOffReceiver; + +import foundation.e.drive.work.FullScanWorker; import static foundation.e.drive.utils.AppConstants.MEDIASYNC_PROVIDER_AUTHORITY; import static foundation.e.drive.utils.AppConstants.SETTINGSYNC_PROVIDER_AUTHORITY; import androidx.annotation.NonNull; +import androidx.work.Constraints; +import androidx.work.ExistingPeriodicWorkPolicy; +import androidx.work.NetworkType; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkManager; /** @@ -45,7 +51,7 @@ import androidx.annotation.NonNull; */ public abstract class CommonUtils { - final private static String TAG = CommonUtils.class.getSimpleName(); + private final static String TAG = CommonUtils.class.getSimpleName(); /** * Set ServiceUncaughtExceptionHandler to be the MainThread Exception Handler @@ -64,23 +70,6 @@ public abstract class CommonUtils { } } - /** - * Unregister from screeOffReceiver component - * - * @param context app context - * @return true if unregistration was successful or false if it encounter an exception - */ - public static boolean unregisterScreenOff(Context context) { - try { - Log.d("TAG", "unregisterReceiver(screenOffReceiver)"); - context.unregisterReceiver(ScreenOffReceiver.getInstance()); - } catch (IllegalArgumentException e) { - Log.w(TAG, "Can't unregister screenOffReceiver "); - return false; - } - return true; - } - /** * This method retrieve Account corresponding to account's name and type @@ -163,7 +152,7 @@ public abstract class CommonUtils { 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())) { + if (!AppConstants.USER_AGENT.equals(OwnCloudClientManagerFactory.getUserAgent()) ){ OwnCloudClientManagerFactory.setUserAgent(AppConstants.USER_AGENT); } @@ -293,4 +282,25 @@ public abstract class CommonUtils { + "\n File can be read?: " + f.canRead() + "\n File can be written?: " + f.canWrite(); } + + /** + * Enqueue a unique periodic worker to look for file to be synchronized (remote files + local files + * @param workManager the instance of workManager + */ + public static void registerPeriodicFullScanWorker(WorkManager workManager){ + final Constraints constraints = new Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .setRequiresBatteryNotLow(true) + .build(); + + final PeriodicWorkRequest periodicFullScanRequest = + new PeriodicWorkRequest.Builder(FullScanWorker.class, + 31, TimeUnit.MINUTES, + 5, TimeUnit.MINUTES) + .setConstraints(constraints) + .addTag(AppConstants.WORK_GENERIC_TAG) + .build(); + + workManager.enqueueUniquePeriodicWork(FullScanWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.KEEP, periodicFullScanRequest); + } } \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/utils/JobUtils.java b/app/src/main/java/foundation/e/drive/utils/JobUtils.java deleted file mode 100644 index 790427eb..00000000 --- a/app/src/main/java/foundation/e/drive/utils/JobUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright © Vincent Bourgmayer (/e/ foundation). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package foundation.e.drive.utils; - -import android.app.job.JobInfo; -import android.app.job.JobScheduler; -import android.content.ComponentName; -import android.content.Context; -import android.util.Log; -import foundation.e.drive.jobs.ScannerJob; - - -/** - * @author Vincent Bourgmayer - */ - - -public abstract class JobUtils { - final private static String TAG = JobUtils.class.getSimpleName(); //Tag for log - public final static int ScannerJobId = 3310; - //public final static int InitializerJobId = 3311; - - /** - * Start the scheduledJob for observing remote's folder - * @param context app or service context - */ - public static void scheduleScannerJob(Context context){ - Log.i(TAG, "scheduleJob"); - /* I. Start periodic checkup */ - ComponentName jobService = new ComponentName( context, ScannerJob.class ); - - JobInfo job = new JobInfo.Builder(ScannerJobId, jobService ) - .setPeriodic(1860000, 30000) //31git minutes and 30 secondes - .setPersisted(true) - .setRequiredNetworkType( JobInfo.NETWORK_TYPE_ANY ) - .build(); - - JobScheduler jobScheduler = context.getSystemService( JobScheduler.class ); - - if ( jobScheduler.schedule( job ) == JobScheduler.RESULT_SUCCESS ) { - Log.d(TAG, "Scheduled job created"); - } else { - Log.e(TAG, "Scheduled job not created"); - } - } - - /** - * Try to stop the scheduledJob - * @param context - */ - public static void stopScheduledJob(Context context, int jobId){ - context.getSystemService( JobScheduler.class ).cancel(jobId); - } - - /** - * tell if the scannerJob is already registered or not - * @param context - * @return true if is registered, false either - */ - public static boolean isScannerJobRegistered(Context context){ - return (context.getSystemService( JobScheduler.class).getPendingJob(ScannerJobId) != null); - } - -} diff --git a/app/src/main/java/foundation/e/drive/work/FullScanWorker.java b/app/src/main/java/foundation/e/drive/work/FullScanWorker.java new file mode 100644 index 00000000..a2424a07 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/work/FullScanWorker.java @@ -0,0 +1,63 @@ +/* + * Copyright © Vincent Bourgmayer (/e/ foundation). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package foundation.e.drive.work; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import foundation.e.drive.services.ObserverService; +import foundation.e.drive.utils.AppConstants; +import foundation.e.drive.utils.CommonUtils; + +/** + * As a first step, this class must replace foundation.e.drive.jobs.ScannerJob + * in order to allow to use Jetpack Work API + * + * In further development it will be a part of Workers that will replace ObserverService + * I will update this header accordingly + * + * @author Vincent Bourgmayer + */ +public class FullScanWorker extends Worker { + private final static String TAG = FullScanWorker.class.getSimpleName(); + public final static String UNIQUE_WORK_NAME = "FullScan"; + + public FullScanWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + + @NonNull + @Override + public Result doWork() { + Log.d(TAG, "doWork(): going to send intent to ObserverService"); + 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, ""); + + final Account mAccount = CommonUtils.getAccount(accountName, accountType, AccountManager.get(this.getApplicationContext())); + + if (mAccount != null && CommonUtils.isSettingsSyncEnabled(mAccount) && CommonUtils.isMediaSyncEnabled(mAccount)) { + final Intent observerServiceIntent = new Intent(this.getApplicationContext(), ObserverService.class); + this.getApplicationContext().startService(observerServiceIntent); + } else { + Log.w(TAG, "Intent for ObserverService not send : account is null or \"settings sync\" & \"media sync\" settings are disabled"); + } + + return Result.success(); + } +} \ No newline at end of file -- GitLab From 7054112058911c52413ac2eb730ab5d6a1eb2b3d Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Fri, 18 Mar 2022 08:19:40 +0000 Subject: [PATCH 02/25] Add FileObserver to detect local file change --- .../foundation/e/drive/EdriveApplication.java | 29 +++ .../FileObservers/FileEventListener.java | 30 +++ .../FileObservers/RecursiveFileObserver.java | 174 ++++++++++++++++++ .../e/drive/models/FileObserver.java | 33 ++++ .../e/drive/services/InitializerService.java | 3 +- .../e/drive/services/ResetService.java | 3 + 6 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java create mode 100644 app/src/main/java/foundation/e/drive/FileObservers/RecursiveFileObserver.java create mode 100644 app/src/main/java/foundation/e/drive/models/FileObserver.java diff --git a/app/src/main/java/foundation/e/drive/EdriveApplication.java b/app/src/main/java/foundation/e/drive/EdriveApplication.java index b6f6ae35..0543800c 100644 --- a/app/src/main/java/foundation/e/drive/EdriveApplication.java +++ b/app/src/main/java/foundation/e/drive/EdriveApplication.java @@ -13,8 +13,11 @@ import android.accounts.AccountManager; import android.app.Application; import android.content.Context; import android.content.SharedPreferences; +import android.os.Environment; import android.util.Log; +import foundation.e.drive.FileObservers.FileEventListener; +import foundation.e.drive.FileObservers.RecursiveFileObserver; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; @@ -25,6 +28,8 @@ import foundation.e.drive.utils.CommonUtils; public class EdriveApplication extends Application { private static final String TAG = "EdriveApplication"; + private RecursiveFileObserver mFileObserver = null; + @Override public void onCreate() { @@ -33,9 +38,14 @@ public class EdriveApplication extends Application { Log.i(TAG, "Starting"); resetOperationManagerSetting(); + final String pathForObserver = Environment.getExternalStorageDirectory().getAbsolutePath(); + mFileObserver = new RecursiveFileObserver(getApplicationContext(), pathForObserver, new FileEventListener()); + SharedPreferences prefs = getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + if (prefs.getString(AccountManager.KEY_ACCOUNT_NAME, null) != null) { Log.d(TAG, "Account already registered"); + startRecursiveFileObserver(); } else { Account mAccount = CommonUtils.getAccount(getString(R.string.eelo_account_type), AccountManager.get(this)); if (mAccount != null) { @@ -56,6 +66,25 @@ public class EdriveApplication extends Application { .apply(); } + + /** + * Start Recursive FileObserver if not already watching + */ + public void startRecursiveFileObserver(){ + if (!mFileObserver.isWatching()) { + mFileObserver.startWatching(); + Log.d(TAG, "Starting RecursiveFileObserver on media's root folder"); + } + else { + Log.w(TAG, "RecursiveFileObserver (for media's root folder) was already running"); + } + } + + public void stopRecursiveFileObserver(){ + mFileObserver.stopWatching(); + Log.d(TAG, "RecursiveFileObserver on media's root folder stops watching "); + } + @Override public void onLowMemory() { super.onLowMemory(); diff --git a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java new file mode 100644 index 00000000..bcceca7b --- /dev/null +++ b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java @@ -0,0 +1,30 @@ +/* + * Copyright © Narinder Rana (/e/ foundation). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package foundation.e.drive.FileObservers; + +import android.os.FileObserver; +import android.util.Log; + +import java.io.File; + +/** + * @author Narinder Rana + * @author vincent Bourgmayer + */ +public class FileEventListener { + private final static String TAG = FileEventListener.class.getSimpleName(); + + public void onEvent(int event, File file) { + if (event == FileObserver.CLOSE_WRITE) { //Event triggered after modification/creation + Log.d(TAG, "CLOSE_WRITE event for :" + file.getName()); + } else if (event == FileObserver.DELETE) { + Log.d(TAG, "DELETE event for :" + file.getName()); + } + } +} diff --git a/app/src/main/java/foundation/e/drive/FileObservers/RecursiveFileObserver.java b/app/src/main/java/foundation/e/drive/FileObservers/RecursiveFileObserver.java new file mode 100644 index 00000000..1e4a9874 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/FileObservers/RecursiveFileObserver.java @@ -0,0 +1,174 @@ +/* + * Copyright © Narinder Rana (/e/ foundation). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package foundation.e.drive.FileObservers; + +import android.content.Context; +import android.os.FileObserver; + +import java.io.File; +import java.io.FileFilter; +import java.util.HashMap; +import java.util.List; +import java.util.Stack; + +import foundation.e.drive.database.DbHelper; +import foundation.e.drive.models.SyncedFolder; + +/** + * @author Narinder Rana + * @author Vincent Bourgmayer + */ +public class RecursiveFileObserver extends FileObserver { + private final HashMap observers = new HashMap<>(); + private static final FileFilter WATCHABLE_DIRECTORIES_FILTER = new FileFilter() { + @Override + public boolean accept(File file) { + return file.isDirectory() && !file.getName().startsWith("."); + } + }; + + private boolean watching = false; + private final Context applicationContext; + private String path; + private int mask; + private FileEventListener listener; + + public RecursiveFileObserver(Context applicationContext, String path, FileEventListener listener) { + this(applicationContext, path, ALL_EVENTS, listener); + } + + public RecursiveFileObserver(Context applicationContext, String path, int mask, FileEventListener listener) { + super(path, mask); + this.path = path; + this.mask = mask | FileObserver.CREATE | FileObserver.DELETE_SELF; + this.listener = listener; + this.applicationContext = applicationContext; + } + + + @Override + public void onEvent(int event, String path) { + File file; + if (path == null) { + file = new File(this.path); + } else { + file = new File(this.path, path); + } + + notify(event, file); + } + + private void notify(int event, File file) { + if (listener != null) { + listener.onEvent(event & FileObserver.ALL_EVENTS, file); + } + } + + @Override + public void startWatching() { + Stack stack = new Stack<>(); + + List mSyncedFolders = DbHelper.getAllSyncedFolders(applicationContext); + if (!mSyncedFolders.isEmpty()){ + for (SyncedFolder syncedFolder:mSyncedFolders){ + stack.push(syncedFolder.getLocalFolder()); + stack.push(syncedFolder.getRemoteFolder()); + } + watching = true; + } + + // Recursively watch all child directories + while (!stack.empty()) { + String parent = stack.pop(); + startWatching(parent); + + File path = new File(parent); + File[] files = path.listFiles(WATCHABLE_DIRECTORIES_FILTER); + if (files != null) { + for (File file : files) { + stack.push(file.getAbsolutePath()); + } + } + } + } + + /** + * Start watching a single file + * @param path + */ + private void startWatching(String path) { + synchronized (observers) { + FileObserver observer = observers.remove(path); + if (observer != null) { + observer.stopWatching(); + } + observer = new SingleFileObserver(path, mask); + observer.startWatching(); + observers.put(path, observer); + } + } + + @Override + public void stopWatching() { + for (FileObserver observer : observers.values()) { + observer.stopWatching(); + } + observers.clear(); + watching = false; + } + + /** + * Stop watching a single file + * @param path + */ + private void stopWatching(String path) { + synchronized (observers) { + FileObserver observer = observers.remove(path); + if (observer != null) { + observer.stopWatching(); + } + } + } + + public boolean isWatching(){ + return watching; + } + + private class SingleFileObserver extends FileObserver { + private String filePath; + + public SingleFileObserver(String path, int mask) { + super(path, mask); + filePath = path; + } + + @Override + public void onEvent(int event, String path) { + File file; + if (path == null) { + file = new File(filePath); + } else { + file = new File(filePath, path); + } + + switch (event & FileObserver.ALL_EVENTS) { + case DELETE_SELF: + RecursiveFileObserver.this.stopWatching(filePath); + break; + case CREATE: + if (WATCHABLE_DIRECTORIES_FILTER.accept(file)) { + RecursiveFileObserver.this.startWatching(file.getAbsolutePath()); + } + break; + } + + RecursiveFileObserver.this.notify(event, file); + } + } +} diff --git a/app/src/main/java/foundation/e/drive/models/FileObserver.java b/app/src/main/java/foundation/e/drive/models/FileObserver.java new file mode 100644 index 00000000..6407b8a6 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/models/FileObserver.java @@ -0,0 +1,33 @@ +/* + * Copyright © Narinder Rana (/e/ foundation). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package foundation.e.drive.models; + +import java.io.File; +import java.io.Serializable; +import java.util.List; + +/** + * @author Narinder Rana + */ +public class FileObserver implements Serializable { + + private List files; + public FileObserver(List files) { + this.files = files; + + } + + public List getFiles() { + return files; + } + + public void setFiles(List files) { + this.files = files; + } +} 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 ad682250..73a23a64 100644 --- a/app/src/main/java/foundation/e/drive/services/InitializerService.java +++ b/app/src/main/java/foundation/e/drive/services/InitializerService.java @@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import foundation.e.drive.EdriveApplication; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.operations.CreateInitialFolderRemoteOperation; import foundation.e.drive.utils.AppConstants; @@ -280,7 +281,7 @@ public class InitializerService extends Service implements OnRemoteOperationList CommonUtils.registerPeriodicFullScanWorker(WorkManager.getInstance(appContext)); //all folder have been created - + ((EdriveApplication) this.getApplication() ).startRecursiveFileObserver(); //Immediatly start ObserverService to not have to wait 30 minutes. Intent observersServiceIntent = new Intent(getApplicationContext(), foundation.e.drive.services.ObserverService.class); diff --git a/app/src/main/java/foundation/e/drive/services/ResetService.java b/app/src/main/java/foundation/e/drive/services/ResetService.java index 7b7c9d4e..3776c6e7 100644 --- a/app/src/main/java/foundation/e/drive/services/ResetService.java +++ b/app/src/main/java/foundation/e/drive/services/ResetService.java @@ -18,6 +18,7 @@ import android.util.Log; import java.io.File; +import foundation.e.drive.EdriveApplication; import foundation.e.drive.database.DbHelper; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; @@ -66,6 +67,8 @@ public class ResetService extends Service { WorkManager.getInstance(this).cancelAllWorkByTag(AppConstants.WORK_GENERIC_TAG); + ( (EdriveApplication) this.getApplication() ).stopRecursiveFileObserver(); + //3. delete DB result = this.deleteDatabase(DbHelper.DATABASE_NAME); Log.d(TAG, "Remove Database: "+result); -- GitLab From 7e96d82b2400232ebbdfbe7adc7f7021c9b927c7 Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Fri, 18 Mar 2022 09:21:11 +0000 Subject: [PATCH 03/25] Transform OperationManagerService into BoundService (SynchronizerService.java) --- app/src/main/AndroidManifest.xml | 2 +- .../foundation/e/drive/EdriveApplication.java | 19 +- .../e/drive/models/DownloadRequest.java | 26 ++ .../e/drive/models/SyncRequest.java | 39 +++ .../drive/operations/ComparableOperation.java | 18 - .../operations/DownloadFileOperation.java | 16 +- .../drive/operations/RemoveFileOperation.java | 16 +- .../drive/operations/UploadFileOperation.java | 26 +- .../e/drive/services/InitializerService.java | 3 + .../e/drive/services/ObserverService.java | 172 +++++---- .../services/OperationManagerService.java | 326 ------------------ .../e/drive/services/ResetService.java | 9 +- .../services/SynchronizationService.java | 266 ++++++++++++++ .../drive/utils/ServiceExceptionHandler.java | 10 - .../operations/UploadFileOperationTest.java | 13 +- .../services/OperationManagerServiceTest.java | 108 ------ 16 files changed, 449 insertions(+), 620 deletions(-) create mode 100644 app/src/main/java/foundation/e/drive/models/DownloadRequest.java create mode 100644 app/src/main/java/foundation/e/drive/models/SyncRequest.java delete mode 100644 app/src/main/java/foundation/e/drive/operations/ComparableOperation.java delete mode 100644 app/src/main/java/foundation/e/drive/services/OperationManagerService.java create mode 100644 app/src/main/java/foundation/e/drive/services/SynchronizationService.java delete mode 100644 app/src/test/java/foundation/e/drive/services/OperationManagerServiceTest.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7337a890..29b9ba12 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -64,7 +64,7 @@ http://www.gnu.org/licenses/gpl.html - + operationsForIntent; - /* Lifecycle Methods */ + private HashMap syncRequests; //integer is SyncedFileState id; Parcelable is the operation + + private SynchronizationService synchronizationService; + private boolean boundToSynchronizationService = false; + private ServiceConnection SynchronizationServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + SynchronizationService.SynchronizationBinder binder = (SynchronizationService.SynchronizationBinder) iBinder; + synchronizationService = binder.getService(); + boundToSynchronizationService = true; + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + Log.e(TAG, "onServiceDisconnected"); + boundToSynchronizationService = false; + } + }; + + /* Lifecycle Methods */ @Override public void onDestroy(){ Log.i(TAG, "onDestroy()"); @@ -92,13 +110,13 @@ public class ObserverService extends Service implements OnRemoteOperationListene initialFolderCounter = prefs.getInt(AppConstants.INITIALFOLDERS_NUMBER, 0); // Check if account is invalid - if(this.mAccount == null){ + if (this.mAccount == null){ Log.w(TAG, "No account registered"); return super.onStartCommand(intent, flags, startId); } //check if user have disable eDrive's sync in account's settings - if(!CommonUtils.isMediaSyncEnabled(mAccount) && !CommonUtils.isSettingsSyncEnabled(mAccount) ){ + if (!CommonUtils.isMediaSyncEnabled(mAccount) && !CommonUtils.isSettingsSyncEnabled(mAccount) ){ Log.w(TAG, "eDrive syncing has been disabled in /e/ account's settings"); return super.onStartCommand(intent, flags, startId); } @@ -112,13 +130,13 @@ public class ObserverService extends Service implements OnRemoteOperationListene } //Check this service isn't already working - if(isWorking){ + if (isWorking){ Log.w(TAG, "ObserverService is already working"); return super.onStartCommand(intent,flags,startId); } //check OperationManagerService isn't working - if(prefs.getBoolean(AppConstants.KEY_OMS_IS_WORKING, false)){ + if (prefs.getBoolean(AppConstants.KEY_OMS_IS_WORKING, false)){ Log.w(TAG, "OperationManagerService is still performing some operation"); return super.onStartCommand(intent,flags, startId); } @@ -142,7 +160,11 @@ public class ObserverService extends Service implements OnRemoteOperationListene Log.w(TAG, "There is no Internet connexion."); return super.onStartCommand( intent, flags, startId ); } - this.operationsForIntent = new HashMap<>(); + this.syncRequests = new HashMap<>(); + + Intent SynchronizationServiceIntent = new Intent(this.getApplicationContext(), SynchronizationService.class); + bindService(SynchronizationServiceIntent, SynchronizationServiceConnection, Context.BIND_AUTO_CREATE); + begin(); return super.onStartCommand( intent, flags, startId ); } @@ -191,7 +213,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene Log.i(TAG, "clearCachedFile()"); //Load subfiles into external cache file File[] fileArray = this.getApplicationContext().getExternalCacheDir().listFiles(new OnlyFileFilter() ); - if(fileArray != null) { + if (fileArray != null) { boolean toRemove; for (int i = -1, size = fileArray.length; ++i < size; ) { toRemove = true; @@ -200,7 +222,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene Log.v(TAG+"_handleCachedFile()", "Deletion of cached file: " + deleteResult); } } - }else{ + } else { Log.e(TAG+"_handleCachedFile()", "Array of cached file is null"); } } @@ -215,7 +237,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene Log.i(TAG, "startScan("+remote+")"); this.mSyncedFolders = loadSyncedFolders(); - if(mSyncedFolders.isEmpty() ){ + if (mSyncedFolders.isEmpty() ){ Log.w(TAG, "List of synced folders is empty"); this.stopSelf(); return; @@ -238,7 +260,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene } catch (IllegalArgumentException e){ Log.e(TAG, e.toString() ); } - }else{ + } else { Log.w(TAG, "OwnCloudClient is null"); return; } @@ -256,22 +278,17 @@ public class ObserverService extends Service implements OnRemoteOperationListene boolean mediaSyncEnabled = CommonUtils.isMediaSyncEnabled(mAccount); boolean settingsSyncedEnabled = CommonUtils.isSettingsSyncEnabled(mAccount); - if(mediaSyncEnabled && settingsSyncedEnabled){ + if (mediaSyncEnabled && settingsSyncedEnabled){ return DbHelper.getAllSyncedFolders(this); - }else if(mediaSyncEnabled){ + } else if (mediaSyncEnabled){ return DbHelper.getSyncedFolderList(this, true); - }else if(settingsSyncedEnabled){ + } else if (settingsSyncedEnabled){ return DbHelper.getSyncedFolderList(this, false); - }else{ + } else { return new ArrayList(); } } - - - - - /** * Handle end of remote Operation * @param operation The RemoteOperation which ends and call this methods @@ -280,7 +297,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result ) { Log.i( TAG, "onRemoteOperationFinish()" ); - if( operation instanceof ListFileRemoteOperation) { + if ( operation instanceof ListFileRemoteOperation) { if (result.isSuccess()) { List resultDatas = result.getData(); if (resultDatas != null) { @@ -300,18 +317,12 @@ public class ObserverService extends Service implements OnRemoteOperationListene } this.startScan(false); - Log.v(TAG, "operationsForIntent contains "+ operationsForIntent.size() ); + Log.v(TAG, "operationsForIntent contains "+ syncRequests.size() ); //After everything has been scanned. Send Intent to OperationmanagerService with data in bundle - if(operationsForIntent != null && !operationsForIntent.isEmpty()) { - Intent OMSIntent = new Intent(this, OperationManagerService.class); - for(Map.Entry entry: operationsForIntent.entrySet()){ - OMSIntent.putExtra(entry.getKey()+"", entry.getValue()); - } - - OMSIntent.putExtra("account", mAccount); - startService(OMSIntent); - }else{ + if (syncRequests != null && !syncRequests.isEmpty()) { + this.synchronizationService.queueOperations(syncRequests.values()); + } else { Log.w(TAG, "There is no file to sync."); getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) .edit() @@ -321,6 +332,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene } this.isWorking = false; + unbindService(SynchronizationServiceConnection); this.stopSelf(); } } @@ -333,7 +345,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene List result = new ArrayList<>(); for(int i = -1, size = this.mSyncedFolders.size(); ++i < size;){ SyncedFolder syncedFolder = this.mSyncedFolders.get(i); - if(syncedFolder.isToSync() ){ + if (syncedFolder.isToSync() ){ result.add( (long) syncedFolder.getId() ); } } @@ -367,7 +379,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene SyncedFileState syncedFileState = syncedFileListIterator.next(); //ignore hidden file from db - if(syncedFileState.isMediaType() && syncedFileState.getName().startsWith(".")){ + if (syncedFileState.isMediaType() && syncedFileState.getName().startsWith(".")){ syncedFileListIterator.remove(); continue; } @@ -392,10 +404,10 @@ public class ObserverService extends Service implements OnRemoteOperationListene syncedFileState.setLastETAG(remoteFile.getEtag()); int affectedRows = DbHelper.manageSyncedFileStateDB(syncedFileState, "UPDATE", this); Log.v(TAG, affectedRows + " syncedFileState.s row in DB has been updated."); - }else { + } else { Log.i(TAG, "Add download operation for file "+syncedFileState.getId()); - DownloadFileOperation downloadFileOperation = new DownloadFileOperation(remoteFile, syncedFileState); - this.operationsForIntent.put(syncedFileState.getId(), downloadFileOperation); + + this.syncRequests.put(syncedFileState.getId(), new DownloadRequest(remoteFile, syncedFileState)); } } syncedFileListIterator.remove(); //we can delete syncedFile from list because its correspondant has already been found and handled @@ -403,7 +415,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene } } - if( correspondant_found )continue; + if ( correspondant_found )continue; //If we get here, RemoteFile is a new file to download Log.v(TAG, "SyncedFileState corresponding to remoteFile not found."); @@ -428,7 +440,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene newRemoteFile.setId(storedId); Log.i(TAG, "Add download operation for new file "+storedId); //Create Download operation and add it into Bundle - this.operationsForIntent.put(storedId, new DownloadFileOperation(remoteFile, newRemoteFile)); + this.syncRequests.put(storedId, new DownloadRequest(remoteFile, newRemoteFile)); } else { Log.w(TAG, "Can't save new remote File in DB. Ignore file."); @@ -458,20 +470,20 @@ public class ObserverService extends Service implements OnRemoteOperationListene //Loop through remaining file state for(int i = -1, size = syncedFileStates.size(); ++i < size; ){ SyncedFileState syncedFileState = syncedFileStates.get(i); - if( !CommonUtils.isThisSyncAllowed( mAccount, syncedFileState.isMediaType() ) ){ + if ( !CommonUtils.isThisSyncAllowed( mAccount, syncedFileState.isMediaType() ) ){ Log.d(TAG, "Sync of current file: "+syncedFileState.getName()+" isn't allowed"); continue; } //Check that file has already been synced fully - if( syncedFileState.isLastEtagStored() && syncedFileState.getLocalLastModified() > 0L) { + if ( syncedFileState.isLastEtagStored() && syncedFileState.getLocalLastModified() > 0L) { //Get local file File file = new File( syncedFileStates.get(i).getLocalPath() ); //Try to remove local file boolean fileExists = file.exists(); - if( fileExists) { + if ( fileExists) { Log.d(TAG, file.getName()+" exists *1"); //delete file int rowAffected = getContentResolver().delete(MediaStore.Files.getContentUri("external"), @@ -483,11 +495,11 @@ public class ObserverService extends Service implements OnRemoteOperationListene } //if it succeed, remove syncedFileState in DB - if(! fileExists ) { + if (! fileExists ) { //It means that file has been correctly deleted from device. So update DB. if (DbHelper.manageSyncedFileStateDB(syncedFileState, "DELETE", this) <= 0) Log.e(TAG, "SyncedFileState row hasn't been deleted from DB"); - }else + } else Log.w(TAG, "local file:"+file.getName()+" still exist and can't be remove"); } } @@ -538,7 +550,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene List folderIdList= new ArrayList<>(); boolean contentToSyncFound = false; //Regenere list of application's package - if(CommonUtils.isSettingsSyncEnabled(mAccount)) generateAppListFile(); + if (CommonUtils.isSettingsSyncEnabled(mAccount)) generateAppListFile(); ListIterator iterator = mSyncedFolders.listIterator() ; @@ -548,13 +560,13 @@ public class ObserverService extends Service implements OnRemoteOperationListene Log.d(TAG, "SyncedFolder :"+syncedFolder.getLibelle()+", "+syncedFolder.getLocalFolder()+", "+syncedFolder.getLastModified()+", "+syncedFolder.isScanLocal()+", "+syncedFolder.getId() ); //Check it's not a hidden file - if(syncedFolder.isMediaType() && CommonUtils.getFileNameFromPath(syncedFolder.getLocalFolder()).startsWith(".")){ + if (syncedFolder.isMediaType() && CommonUtils.getFileNameFromPath(syncedFolder.getLocalFolder()).startsWith(".")){ iterator.remove(); continue; } //Check it can be scann from local - if(!syncedFolder.isScanLocal()){ + if (!syncedFolder.isScanLocal()){ iterator.remove(); continue; } @@ -567,7 +579,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene if (syncedFolder_id > 0) { Log.v(TAG, "Folder has been registered in DB"); syncedFolder.setId(syncedFolder_id); - }else { + } else { Log.w(TAG, "syncedFolder " + syncedFolder.getLocalFolder() + " remove iterator because it hasn't been registered in DB or already stored"); iterator.remove(); continue; @@ -578,7 +590,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene Log.d(TAG, "Local Folder (last modified / exists): "+localFolder.lastModified()+", "+localFolder.exists() ); //Check if local folder exists - if(!localFolder.exists()){ + if (!localFolder.exists()){ Log.v(TAG, "local folder doesn't exist anymore . So content has change"); contentToSyncFound = true; folderIdList.add( (long) syncedFolder.getId() ); @@ -588,7 +600,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene boolean folderHasChange = false; //consider by default that file hadn't change //Check if folder had change - if(localFolder.lastModified() > syncedFolder.getLastModified() ) { //compare last modified date + if (localFolder.lastModified() > syncedFolder.getLastModified() ) { //compare last modified date Log.v(TAG, "local folder has changed"); syncedFolder.setLastModified( localFolder.lastModified() ); //@Todo: it would be better to set it after all it's content has been synced contentToSyncFound = true; //at least one dir has changed @@ -603,7 +615,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene Log.v(TAG, "loop through subfiles"); for (int i = -1, subEltSize = (subElements != null)? subElements.length: 0; ++i < subEltSize; ) { File subElt = subElements[i]; - if(subElt == null) continue; + if (subElt == null) continue; if (subElt.isDirectory()) { //if its a subfolder add it to syncedFolder list //if a subfolder is found, add it to syncedFolder list Log.v(TAG, "subfile "+subElt.getAbsolutePath()+" is a directory."); @@ -621,12 +633,12 @@ public class ObserverService extends Service implements OnRemoteOperationListene } //end of iterator loop - if(contentToSyncFound) { + if (contentToSyncFound) { DbHelper.updateSyncedFolders(mSyncedFolders, this); //@ToDo: maybe do this when all contents will be synced. List syncedFileStates = DbHelper.getSyncedFileStatesByFolders(this, folderIdList); - if(!syncedFileStates.isEmpty() || !fileList.isEmpty() ) { + if (!syncedFileStates.isEmpty() || !fileList.isEmpty() ) { handleLocalFiles(fileList, syncedFileStates); } } @@ -662,7 +674,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene SyncedFileState syncedFileState = syncedFileListIterator.next(); //Ignore hidden media file store in DB - if(syncedFileState.isMediaType() && syncedFileState.getName().startsWith(".")){ + if (syncedFileState.isMediaType() && syncedFileState.getName().startsWith(".")){ syncedFileListIterator.remove(); continue; } @@ -670,34 +682,20 @@ public class ObserverService extends Service implements OnRemoteOperationListene Log.v(TAG, syncedFileState.getLocalPath()+", "+syncedFileState.getId()+", "+syncedFileState.getLocalLastModified()); //if syncedFileState correspond to local file - if( syncedFileState.getLocalPath().equals( filePath ) ){ + if ( syncedFileState.getLocalPath().equals( filePath ) ){ correspondant_found = true; //If no etag is stored in sfs, the file hasn't been sync up to server. then do upload - if( syncedFileState.getLocalLastModified() < localFile.lastModified() || !syncedFileState.isLastEtagStored()){ - Log.d(TAG+"_handleLocalFiles()", syncedFileState.getName()+" file has been modified or never sync" ); - boolean checkEtag = false; - - //Look for folder to know if the folder can be scan remotly. - for(int folderIndex =-1, size = mSyncedFolders.size();++folderIndex < size;) { - - final SyncedFolder syncedFolder = mSyncedFolders.get(folderIndex); - if (syncedFolder.getId() == syncedFileState.getSyncedFolderId()) { - //Parent folder has been found - checkEtag = syncedFolder.isScanRemote(); - break; - } - } - Log.i(TAG, "Add upload operation for file "+syncedFileState.getId()); - UploadFileOperation uploadFileOperation = new UploadFileOperation(syncedFileState, checkEtag ); - this.operationsForIntent.put(syncedFileState.getId(), uploadFileOperation); + if ( syncedFileState.getLocalLastModified() < localFile.lastModified() || !syncedFileState.isLastEtagStored()){ + Log.i(TAG, "Add upload request for file "+syncedFileState.getId()); + this.syncRequests.put(syncedFileState.getId(), new SyncRequest(syncedFileState, SyncRequest.Type.UPLOAD)); } // No need to reloop on it. syncedFileListIterator.remove(); break; } } - if( correspondant_found ) continue; + if ( correspondant_found ) continue; //if no correspondance, then it is a new file Log.v(TAG, "this is a new file to sync"); @@ -707,19 +705,18 @@ public class ObserverService extends Service implements OnRemoteOperationListene //look into synced folders if folder path exist for(SyncedFolder syncedFolder : mSyncedFolders){ - if(syncedFolder.getLocalFolder().equals(parentPath)){ + if (syncedFolder.getLocalFolder().equals(parentPath)){ //create the syncedFile State SyncedFileState newSyncedFileState = new SyncedFileState(-1, localFile.getName(), filePath, syncedFolder.getRemoteFolder() + localFile.getName(), "", 0, syncedFolder.getId(), syncedFolder.isMediaType()); //Store it in DB int storedId = DbHelper.manageSyncedFileStateDB(newSyncedFileState, "INSERT", this); - if(storedId > 0){ + if (storedId > 0){ newSyncedFileState.setId( storedId ); Log.i(TAG, "Add upload operation for new file "+storedId); - //create UploadOperation and add it into bundle - UploadFileOperation uploadOperation = new UploadFileOperation(newSyncedFileState, syncedFolder.isScanRemote()); - this.operationsForIntent.put(storedId, uploadOperation); - }else{ + + this.syncRequests.put(storedId, new SyncRequest(newSyncedFileState, SyncRequest.Type.UPLOAD)); + } else { Log.w(TAG, "The new file to synced cannot be store in DB. Ignore it"); } break; @@ -738,16 +735,15 @@ public class ObserverService extends Service implements OnRemoteOperationListene Log.i(TAG, "handleLocalRemainingSyncedFileState(...)"); //Loop through remaining SyncedFileState for(SyncedFileState fileState : syncedFileStates){ - if(fileState.isLastEtagStored() && fileState.getLocalLastModified() > 0L){ + if (fileState.isLastEtagStored() && fileState.getLocalLastModified() > 0L){ //try to get File File file = new File(fileState.getLocalPath()); Log.v(TAG, "File : "+file.getAbsolutePath()+","+file.exists()); - if(file.exists()){ + if (file.exists()){ Log.w(TAG, "The file still exist. There is a problem!"); - }else{ - Log.i(TAG, "Add remove operation for file "+fileState.getId()); - RemoveFileOperation removeOperation = new RemoveFileOperation(fileState); - this.operationsForIntent.put(fileState.getId(), removeOperation); + } else { + Log.i(TAG, "Add remote remove request for file "+fileState.getId()); + this.syncRequests.put(fileState.getId(), new SyncRequest(fileState, SyncRequest.Type.REMOTE_DELETE)); } } } diff --git a/app/src/main/java/foundation/e/drive/services/OperationManagerService.java b/app/src/main/java/foundation/e/drive/services/OperationManagerService.java deleted file mode 100644 index c523f54c..00000000 --- a/app/src/main/java/foundation/e/drive/services/OperationManagerService.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright © Vincent Bourgmayer (/e/ foundation). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package foundation.e.drive.services; - -import android.accounts.Account; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.Parcelable; -import android.util.Log; - -import androidx.annotation.Nullable; - -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.lang.ref.WeakReference; -import java.util.Hashtable; -import java.util.concurrent.ConcurrentLinkedDeque; - -import foundation.e.drive.database.DbHelper; -import foundation.e.drive.operations.ComparableOperation; -import foundation.e.drive.operations.DownloadFileOperation; -import foundation.e.drive.operations.RemoveFileOperation; -import foundation.e.drive.operations.UploadFileOperation; -import foundation.e.drive.utils.AppConstants; -import foundation.e.drive.utils.CommonUtils; -import foundation.e.drive.utils.DavClientProvider; -import foundation.e.drive.utils.ServiceExceptionHandler; - -/** - * @author Vincent Bourgmayer - * Service to do upload, remove and download operation. - */ -public class OperationManagerService extends Service implements OnRemoteOperationListener{ - private final static String TAG = OperationManagerService.class.getSimpleName(); - - private int workerAmount = 0; //Number of thread available to execute RemoteOperation - private boolean[] mThreadWorkingState; //State of the threads; true mean the thread is working - private Thread[] mThreadPool; //The threads to use - - private ConcurrentLinkedDeque mOperationsQueue; // Queue of Operation - private Hashtable mStartedOperations; //Operations which are running - - private OperationManagerHandler mHandler; - private OwnCloudClient mClient; //ClientObject - private Account mAccount; //Will be used soon - - @Override - public void onDestroy() { - Log.i(TAG, "onDestroy()"); - super.onDestroy(); - } - - - /** - * Start to run all threads - */ - private void startAllThreads(){ - Log.i(TAG, "startAllThreads"); - for(int i =-1; ++i < workerAmount;){ - this.startWork(i); - } - } - - /** - * retrieve an operation from queue and execute it if a thread is available. - * @param threadIndex index of thread which execute job. - */ - private synchronized void startWork( int threadIndex ){ - Log.i(TAG, "startWork("+threadIndex+")" ); - - //Check if operation queue is empty - if(mOperationsQueue.isEmpty()){ - boolean stillWorking = false; - //check that all other thread has finished. - for(boolean a : this.mThreadWorkingState){ - if(a){ - stillWorking = a; - break; - } - } - - if(!stillWorking) { - Log.i(TAG, "Operation queue is empty. all jobs Done. The End"); - //Register timestamp to allow to calculate min delay between two sync - getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) - .edit() - .putLong(AppConstants.KEY_LAST_SYNC_TIME, System.currentTimeMillis()) - .putBoolean(AppConstants.KEY_OMS_IS_WORKING, false) - .apply(); - - stopSelf(); - return; - } - } - //start the new Job - if( !mThreadWorkingState[threadIndex] && CommonUtils.haveNetworkConnexion( getApplicationContext() ) ) { //check if the thread corresponding to threadIndex isn't already working - - ComparableOperation operation = this.mOperationsQueue.poll(); //return null if deque is empty - if (operation != null) { - Log.v(TAG, " an operation has been poll from queue"); - - if( CommonUtils.isThisSyncAllowed(mAccount, operation.isMediaType() ) ) { - mStartedOperations.put(operation.toRemoteOperation(), threadIndex); - this.mThreadPool[threadIndex] = operation.toRemoteOperation().execute(mClient, this, this.mHandler); - this.mThreadWorkingState[threadIndex] = true; - } - } - } //else : thread is already running or no network connexion - } - - /** - * Called when a remoteOperation finish - * @param operation the operation which finished - * @param result the result of the operation - */ - @Override - public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { - Log.i(TAG, "onRemoteOperationFinish()"); - - // Start an another operation with the thread which has run this one - Integer threadIndex = this.mStartedOperations.remove(operation); - if(threadIndex != null) { - this.mThreadWorkingState[threadIndex] = false; - this.startWork(threadIndex); - } - - if(operation instanceof RemoveFileOperation){ - if( result.isSuccess() ) { - DbHelper.manageSyncedFileStateDB( ( ( RemoveFileOperation ) operation ).getSyncedFileState(), - "DELETE", this); - } - }else { - String operationClassName = operation.getClass().getSimpleName(); - switch (result.getCode()) { - case OK: - Log.d(TAG, operationClassName + " Succeed"); - break; - case SYNC_CONFLICT: - //Case specific to UploadFileOperation - Log.e(TAG, operationClassName+" : Sync_conflict : File is already up to date"); - break; - case INVALID_OVERWRITE: - Log.e(TAG, operationClassName + " => invalid_overwrite :\n remote file and local file doesn't have the same size"); - break; - case UNKNOWN_ERROR: - if (operation instanceof UploadFileOperation) { - if(result.getData() != null) { - int rowAffected = DbHelper.forceFoldertoBeRescan(((Long) result.getData().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"); - } - } else if (operation instanceof DownloadFileOperation) { - Log.e(TAG, " Download: Unknown_error : failed"); - } - break; - case FORBIDDEN: - if (operation instanceof UploadFileOperation) { - - if(result.getData() != null) { - int rowAffected = DbHelper.forceFoldertoBeRescan(((Long) result.getData().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"); - } - } else if (operation instanceof DownloadFileOperation) { - Log.e(TAG, "Download : Forbidden: Can't get syncedFileState, no local path defined"); - } - break; - case QUOTA_EXCEEDED: - //Case specific to UploadFileOperation - Log.w(TAG, "Quota_EXCEEDED"); - - NotificationManager nM = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); - - Notification notif = new Notification.Builder(this, AppConstants.notificationChannelID) - .setContentIntent(PendingIntent.getActivity(getApplicationContext(), - 0, - new Intent(Intent.ACTION_VIEW, mClient.getBaseUri()), - 0)) - .setContentText("Your drive lacks of space. Tap to check " + mClient.getBaseUri()) - .setSmallIcon(android.R.drawable.stat_sys_warning) - .build(); - - nM.notify(1,notif ); - break; - case FILE_NOT_FOUND: - //Case specific to DownloadFileOperation - Log.e(TAG, operationClassName+" : File_not_found: File not found after download"); - break; - case ETAG_UNCHANGED: - //Case specific to DownloadFileOperation - Log.e(TAG, operationClassName+" : Sync_conflict: File is already up to date"); - break; - - } //Close switch - } //Close else - } - - /** - * Handler for the class - */ - static class OperationManagerHandler extends Handler { - private final String TAG = OperationManagerHandler.class.getSimpleName(); - - private final WeakReference mOperationServiceWeakRef; - - OperationManagerHandler(OperationManagerService mOperationService){ - this.mOperationServiceWeakRef = new WeakReference<>(mOperationService); - } - - @Override - public void handleMessage(Message msg) { - Log.i(TAG, "handler.handleMessage()"); - Bundle data = msg.getData(); - - mOperationServiceWeakRef.get() - .mThreadWorkingState[data.getInt("thread index")] = data.getBoolean("mThreadWorkingState"); - } - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Log.i(TAG, "onStartCommand()"); - - try{ - CommonUtils.setServiceUnCaughtExceptionHandler(this); - - if (null == intent || null == intent.getAction ()) { - String source = null == intent ? "intent" : "action"; - Log.e (TAG, source + " was null, flags=" + flags + " bits=" + Integer.toBinaryString (flags)); - //return START_STICKY; - } - - Bundle extras = intent.getExtras(); - Log.d(TAG, "OperationManagerService recieved "+(extras == null ? "null extras": extras.size()+" operations to perform") ); - - if(extras != null) { - //Load operation from intent - this.mOperationsQueue = new ConcurrentLinkedDeque<>(); - for (String key : extras.keySet()) { - - Parcelable parcelableObject = extras.getParcelable(key); - if (key.equals("account")) { - this.mAccount = (Account) parcelableObject; - } else if (parcelableObject.getClass().getName().equals(DownloadFileOperation.class.getName())) { - DownloadFileOperation download = (DownloadFileOperation) parcelableObject; - download.setContext(getApplicationContext()); - mOperationsQueue.add(download); - } else if (parcelableObject.getClass().getName().equals(UploadFileOperation.class.getName())) { - UploadFileOperation upload = (UploadFileOperation) parcelableObject; - upload.setContext(getApplicationContext()); - mOperationsQueue.add(upload); - } else if (parcelableObject.getClass().getName().equals(RemoveFileOperation.class.getName())) { - mOperationsQueue.add((RemoveFileOperation) parcelableObject); - } - } - - if(mAccount == null || mOperationsQueue.isEmpty()){ - Log.w(TAG, "No account or Operation queue is empty"); - return super.onStartCommand(intent, flags, startId); - } - - //Initialize class's field - this.workerAmount = 4; //This variable could be replace later by an option in settings - - this.mThreadPool = new Thread[workerAmount]; - this.mThreadWorkingState = new boolean[workerAmount]; - this.mHandler = new OperationManagerHandler(this); - this.mStartedOperations = new Hashtable(); - - mClient = DavClientProvider.getInstance().getClientInstance(mAccount, getApplicationContext()); - if (mClient != null) { - getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) - .edit() - .putBoolean(AppConstants.KEY_OMS_IS_WORKING, true) - .apply(); - - startAllThreads(); - } else { - Log.w(TAG, "No Client, Can't Work!"); - stopSelf(); - } - }else{ - Log.w(TAG, "Intent's extras is null."); - } - }catch (Exception ex){ - Log.e("Exception", ex.getMessage()); - ex.printStackTrace(); - } - - return super.onStartCommand(intent, flags, startId); - } - - - @Nullable - @Override - public IBinder onBind(Intent intent) { - throw new UnsupportedOperationException(); - } - - @Override - public void onLowMemory() { - Log.w(TAG, "System is low on memory. Service might get killed. Setting KEY_OMS_IS_WORKING to false"); - getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE).edit() - .putBoolean(AppConstants.KEY_OMS_IS_WORKING, false) - .apply(); - } -} diff --git a/app/src/main/java/foundation/e/drive/services/ResetService.java b/app/src/main/java/foundation/e/drive/services/ResetService.java index 3776c6e7..99aad5fe 100644 --- a/app/src/main/java/foundation/e/drive/services/ResetService.java +++ b/app/src/main/java/foundation/e/drive/services/ResetService.java @@ -59,11 +59,14 @@ public class ResetService extends Service { result = getApplicationContext().stopService( stopperIntent ); Log.d(TAG, "stop InitializerService: "+result); - stopperIntent = new Intent(getApplicationContext(), OperationManagerService.class); //@todo try to replace it by stopperIntent.setClassName(getApplicationContext, OperationManagerService.class) + stopperIntent = new Intent(getApplicationContext(), SynchronizationService.class); //@todo try to replace it by stopperIntent.setClassName(getApplicationContext, OperationManagerService.class) + result = getApplicationContext().stopService( stopperIntent ); + Log.d(TAG, "stop SynchronizationService: "+result); - result = getApplicationContext().stopService( stopperIntent ); - Log.d(TAG, "stop OperationManagerService: "+result); + WorkManager.getInstance(this).cancelAllWorkByTag(AppConstants.WORK_GENERIC_TAG); + + ( (EdriveApplication) this.getApplication() ).stopRecursiveFileObserver(); WorkManager.getInstance(this).cancelAllWorkByTag(AppConstants.WORK_GENERIC_TAG); diff --git a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java new file mode 100644 index 00000000..d532786e --- /dev/null +++ b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java @@ -0,0 +1,266 @@ +/* + * Copyright © Vincent Bourgmayer (/e/ foundation). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ +package foundation.e.drive.services; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.util.Log; + +import androidx.annotation.Nullable; + +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.lang.ref.WeakReference; +import java.util.Collection; +import java.util.Hashtable; +import java.util.concurrent.ConcurrentLinkedDeque; + +import foundation.e.drive.database.DbHelper; +import foundation.e.drive.models.DownloadRequest; +import foundation.e.drive.models.SyncRequest; +import foundation.e.drive.models.SyncedFileState; +import foundation.e.drive.operations.DownloadFileOperation; +import foundation.e.drive.operations.RemoveFileOperation; +import foundation.e.drive.operations.UploadFileOperation; +import foundation.e.drive.utils.AppConstants; +import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DavClientProvider; + +/** + * @author Vincent Bourgmayer + */ +public class SynchronizationService extends Service implements OnRemoteOperationListener { + private final static String TAG = SynchronizationService.class.getSimpleName(); + private final SynchronizationBinder binder = new SynchronizationBinder(); + + private ConcurrentLinkedDeque syncedRequestQueue; + private Hashtable startedOperations; //Operations which are running + private Account account; + private final int workerAmount = 4; + private boolean[] threadWorkingState; //State of the threads; true mean the thread is working + private Thread[] threadPool; + private OwnCloudClient client; + private OperationHandler handler; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.d(TAG, "onStartCommand()"); + + final SharedPreferences prefs = getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + + if (prefs.getString(AccountManager.KEY_ACCOUNT_NAME, null) == null) { + Log.w(TAG, "No account available. Stop SynchronizationService"); + stopSelf(); + return START_NOT_STICKY; + } + + + account = (Account) intent.getParcelableExtra("account"); + syncedRequestQueue = new ConcurrentLinkedDeque<>(); + startedOperations = new Hashtable<>(); + threadPool = new Thread[workerAmount]; + threadWorkingState = new boolean[workerAmount]; + client = DavClientProvider.getInstance().getClientInstance(account, getApplicationContext()); + handler = new OperationHandler(this); + + return START_REDELIVER_INTENT; + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + @Override + public void onLowMemory() { + Log.w(TAG, "System is low on memory. Service might get killed. Setting KEY_OMS_IS_WORKING to false"); + getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE).edit() + .putBoolean(AppConstants.KEY_OMS_IS_WORKING, false) + .apply(); + } + + public boolean queueOperation(SyncRequest request){ + return syncedRequestQueue.add(request); + } + + public boolean queueOperations(Collection requests){ + return syncedRequestQueue.addAll(requests); + } + + public void startSynchronization(){ + Log.d(TAG, "startAllThreads"); + for(int i =-1; ++i < workerAmount;){ + this.startWorker(i); + } + } + + private void startWorker(int threadIndex){ + if (syncedRequestQueue.isEmpty()) return; + if (!threadWorkingState[threadIndex] && CommonUtils.haveNetworkConnexion(getApplicationContext())) { //check if the thread corresponding to threadIndex isn't already working + + final SyncRequest request = this.syncedRequestQueue.poll(); //return null if deque is empty + + final RemoteOperation operation = this.createRemoteOperation(request); + + if (operation != null) { + Log.v(TAG, " an operation has been poll from queue"); + + if (CommonUtils.isThisSyncAllowed(account, request.getSyncedFileState().isMediaType())) { + startedOperations.put(operation, threadIndex); + threadPool[threadIndex] = operation.execute(client, this, handler); + threadWorkingState[threadIndex] = true; + } + } + } + } + + @Override + public void onRemoteOperationFinish(RemoteOperation callerOperation, RemoteOperationResult result) { + Log.d(TAG, "onRemoteOperationFinish()"); + Integer threadIndex = this.startedOperations.remove(callerOperation); + if (threadIndex != null) { + this.threadWorkingState[threadIndex] = false; + this.startWorker(threadIndex); + } + + if (callerOperation instanceof RemoveFileOperation){ + if ( result.isSuccess() ) { + DbHelper.manageSyncedFileStateDB( ( ( RemoveFileOperation ) callerOperation ).getSyncedFileState(), + "DELETE", this); + } + } else { + String operationClassName = callerOperation.getClass().getSimpleName(); + switch (result.getCode()) { + case OK: + Log.d(TAG, operationClassName + " Succeed"); + break; + case SYNC_CONFLICT: + //Case specific to UploadFileOperation + Log.e(TAG, operationClassName+" : Sync_conflict : File is already up to date"); + break; + case INVALID_OVERWRITE: + Log.e(TAG, operationClassName + " => invalid_overwrite :\n remote file and local file doesn't have the same size"); + break; + case UNKNOWN_ERROR: + if (callerOperation instanceof UploadFileOperation) { + if(result.getData() != null) { + int rowAffected = DbHelper.forceFoldertoBeRescan(((Long) result.getData().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"); + } + } else if (callerOperation instanceof DownloadFileOperation) { + Log.e(TAG, " Download: Unknown_error : failed"); + } + break; + case FORBIDDEN: + if (callerOperation instanceof UploadFileOperation) { + if (result.getData() != null) { + int rowAffected = DbHelper.forceFoldertoBeRescan(((Long) result.getData().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"); + } + } else if (callerOperation instanceof DownloadFileOperation) { + Log.e(TAG, "Download : Forbidden: Can't get syncedFileState, no local path defined"); + } + break; + case QUOTA_EXCEEDED: + //Case specific to UploadFileOperation + Log.w(TAG, "Quota_EXCEEDED"); + + NotificationManager nM = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); + + Notification notif = new Notification.Builder(this, AppConstants.notificationChannelID) + .setContentIntent(PendingIntent.getActivity(getApplicationContext(), + 0, + new Intent(Intent.ACTION_VIEW, client.getBaseUri()), + 0)) + .setContentText("Your drive lacks of space. Tap to check " + client.getBaseUri()) + .setSmallIcon(android.R.drawable.stat_sys_warning) + .build(); + + nM.notify(1,notif ); + break; + case FILE_NOT_FOUND: + //Case specific to DownloadFileOperation + Log.e(TAG, operationClassName+" : File_not_found: File not found after download"); + break; + case ETAG_UNCHANGED: + //Case specific to DownloadFileOperation + Log.e(TAG, operationClassName+" : Sync_conflict: File is already up to date"); + break; + + } + } + } + + private RemoteOperation createRemoteOperation(SyncRequest request){ + RemoteOperation operation; + switch (request.getOperationType()){ + case UPLOAD: + final SyncedFileState sfs = request.getSyncedFileState(); + operation = new UploadFileOperation(sfs); + break; + case DOWNLOAD: + final DownloadRequest downloadRequest = (DownloadRequest) request; + operation = new DownloadFileOperation(downloadRequest.getRemoteFile(), downloadRequest.getSyncedFileState()); + break; + case REMOTE_DELETE: + default: + operation = null; + break; + } + return operation; + } + + + /** + * Handler for the class + */ + static class OperationHandler extends Handler { + private final String TAG = SynchronizationService.OperationHandler.class.getSimpleName(); + + private final WeakReference serviceWeakRef; + + OperationHandler(SynchronizationService mOperationService){ + serviceWeakRef = new WeakReference<>(mOperationService); + } + + @Override + public void handleMessage(Message msg) { + Log.i(TAG, "handler.handleMessage()"); + Bundle data = msg.getData(); + + serviceWeakRef.get() + .threadWorkingState[data.getInt("thread index")] = data.getBoolean("mThreadWorkingState"); + } + } + + public class SynchronizationBinder extends Binder{ + SynchronizationService getService(){ + return SynchronizationService.this; + } + } +} diff --git a/app/src/main/java/foundation/e/drive/utils/ServiceExceptionHandler.java b/app/src/main/java/foundation/e/drive/utils/ServiceExceptionHandler.java index 250e791b..d94e1c21 100644 --- a/app/src/main/java/foundation/e/drive/utils/ServiceExceptionHandler.java +++ b/app/src/main/java/foundation/e/drive/utils/ServiceExceptionHandler.java @@ -7,7 +7,6 @@ */ package foundation.e.drive.utils; import android.app.Service; -import android.content.Context; import android.os.Environment; import android.util.Log; @@ -18,8 +17,6 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.lang.Thread.UncaughtExceptionHandler; -import foundation.e.drive.services.OperationManagerService; - /** * @author Vincent Bourgmayer */ @@ -48,13 +45,6 @@ public class ServiceExceptionHandler implements UncaughtExceptionHandler{ @Override public void uncaughtException(Thread t, Throwable e) { Log.d(TAG, "Service class: "+service.getClass().getSimpleName()); - //IF OMS is crashing, set settings that it runs to false; - if(service.getClass().getSimpleName().equals(OperationManagerService.class.getSimpleName())){ - service.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) - .edit() - .putBoolean(AppConstants.KEY_OMS_IS_WORKING, false) - .apply(); - } if(isExternalStorageAvailable() && !isExternalStorageReadOnly()){ //Get TimeStamp 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 1c01da6f..fbf30d4f 100644 --- a/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java +++ b/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java @@ -161,7 +161,7 @@ public class UploadFileOperationTest { boolean checkEtag = false; - UploadFileOperation testOperation = new UploadFileOperation(syncedFileStates.get(0), checkEtag); + UploadFileOperation testOperation = new UploadFileOperation(syncedFileStates.get(0)); testOperation.setContext(RuntimeEnvironment.application); //Without it, it won't update database RemoteOperationResult result = testOperation.execute(client); @@ -189,9 +189,10 @@ public class UploadFileOperationTest { @Test public void nullSyncedFileState_shouldFail(){ + 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 - UploadFileOperation testOperation = new UploadFileOperation(null, false); + UploadFileOperation testOperation = new UploadFileOperation(syncedFileState); testOperation.setContext(RuntimeEnvironment.application); RemoteOperationResult result = testOperation.execute(client); @@ -212,7 +213,7 @@ public class UploadFileOperationTest { Assert.assertTrue("SyncedFileState loaded from DB must have an empty Etag", sfs_fromDB.getLastETAG().isEmpty()); boolean checkEtag = false; - UploadFileOperation testOperation = new UploadFileOperation(syncedFileStates.get(0), checkEtag); + UploadFileOperation testOperation = new UploadFileOperation(syncedFileStates.get(0)); File smallFile = new File(sfs_fromDB.getLocalPath()); Assert.assertTrue("Local file deletion return false instead of true", smallFile.delete()); @@ -230,7 +231,7 @@ public class UploadFileOperationTest { //long freeQuota = getUserRemoteFreeQuota(); Assert.assertFalse("Reading remote free quota fails"+userFreeQuota, -1 == userFreeQuota); //We don't care of parameter of UploadFileOperation's constructor - RemoteOperationResult actualResult = new UploadFileOperation(Mockito.mock(SyncedFileState.class), false) + RemoteOperationResult actualResult = new UploadFileOperation(Mockito.mock(SyncedFileState.class)) .checkAvailableSpace(client, (userFreeQuota+1)); Assert.assertEquals("Quota check ("+ userFreeQuota+"vs"+(userFreeQuota+1)+") failed", RemoteOperationResult.ResultCode.QUOTA_EXCEEDED, actualResult.getCode()); } @@ -245,7 +246,7 @@ public class UploadFileOperationTest { //long freeQuota = getUserRemoteFreeQuota(); Assert.assertFalse("Reading remote free quota fails"+userFreeQuota, -1 == userFreeQuota); - RemoteOperationResult actualResult = new UploadFileOperation(Mockito.mock(SyncedFileState.class), false) + RemoteOperationResult actualResult = new UploadFileOperation(Mockito.mock(SyncedFileState.class)) .checkAvailableSpace(client, userFreeQuota); Assert.assertEquals("Quota check ("+ userFreeQuota+" vs "+userFreeQuota+") failed", RemoteOperationResult.ResultCode.QUOTA_EXCEEDED, @@ -262,7 +263,7 @@ public class UploadFileOperationTest { //long freeQuota = getUserRemoteFreeQuota(); Assert.assertFalse("Reading remote free quota fails "+userFreeQuota, -1 == userFreeQuota); - RemoteOperationResult actualResult = new UploadFileOperation(Mockito.mock(SyncedFileState.class), false) + RemoteOperationResult actualResult = new UploadFileOperation(Mockito.mock(SyncedFileState.class)) .checkAvailableSpace(client, (userFreeQuota-1)); Assert.assertEquals("Quota check ("+ userFreeQuota+" vs "+(userFreeQuota-1)+") failed", RemoteOperationResult.ResultCode.OK, diff --git a/app/src/test/java/foundation/e/drive/services/OperationManagerServiceTest.java b/app/src/test/java/foundation/e/drive/services/OperationManagerServiceTest.java deleted file mode 100644 index 0a2fb8f7..00000000 --- a/app/src/test/java/foundation/e/drive/services/OperationManagerServiceTest.java +++ /dev/null @@ -1,108 +0,0 @@ -package foundation.e.drive.services; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.job.JobScheduler; -import android.content.Context; -import android.content.Intent; -import android.net.ConnectivityManager; - -import com.owncloud.android.lib.resources.files.model.RemoteFile; - -import junit.framework.Assert; - -import org.junit.Test; -import org.robolectric.Robolectric; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.shadows.ShadowLog; - -import java.util.List; - -import foundation.e.drive.TestUtils; -import foundation.e.drive.database.DbHelper; -import foundation.e.drive.models.SyncedFileState; -import foundation.e.drive.operations.DownloadFileOperation; -import foundation.e.drive.utils.AppConstants; - - -public class OperationManagerServiceTest extends AbstractServiceIT{ - - public OperationManagerServiceTest(){ - mServiceController = Robolectric.buildService(OperationManagerService.class); - mService = mServiceController.get(); - context = RuntimeEnvironment.application; - 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); - } - - - /** - * Check that the service stop quickly if no intent is provided - */ - @Test - public void noIntent_shouldStop(){ - mServiceController.create(); - mService.onStartCommand(null,0, 0); - - List logs = ShadowLog.getLogs(); - ShadowLog.LogItem lastLog = logs.get(logs.size()-2); - - Assert.assertEquals("Expected last log was: 'intent was null, flags=0 bits=0' but got:"+lastLog.msg, "intent was null, flags=0 bits=0", lastLog.msg); - } - - - /** - * Start the service with an intent that doesn't contains list of extra data - * (meaning no account and no synchronisation to perform) - */ - @Test - public void intentWithoutExtras_shouldStop(){ - mServiceController.create(); - mService.onStartCommand(new Intent(), 0, 0); - - List logs = ShadowLog.getLogs(); - ShadowLog.LogItem lastLog = logs.get(logs.size()-1); - - Assert.assertEquals("Expected last log was: 'Intent's extras is null.' but got:"+lastLog.msg, "Intent's extras is null.", lastLog.msg); - } - - - /** - * Start the OperationmanagerService with File to upload but no account provided - * in the intent. - * Failure is expected - */ - @Test - public void noAccount_shouldFail(){ - prepareValidAccount(); - final Account account = TestUtils.getValidAccount(); - final Intent intent = new Intent("dummyAction"); - - intent.putExtra("0", - new DownloadFileOperation( - new RemoteFile("/eDrive-test/coco.txt"), - new SyncedFileState(3, "coco.txt", "/tmp/eDrive-test/", "/eDrive-test/coco.txt","", -1, 1, true ) - ) - ); - - - intent.putExtra("account", account); - - final boolean accountRemoved = accountManager.removeAccountExplicitly(account); - Assert.assertTrue("Account removal should return true but returned false", accountRemoved); - - mServiceController.create(); - mService.onStartCommand(intent, 0,0); - - List logs = ShadowLog.getLogs(); - ShadowLog.LogItem lastLog = logs.get(logs.size()-1); - Assert.assertEquals("Expected last log was: 'No Client, Can't Work!' but got:"+lastLog.msg, "No Client, Can't Work!", lastLog.msg); - - } - - -} -- GitLab From 022327c6ddd10e72f78416ca107cdf0f8f1824ff Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Mon, 21 Mar 2022 16:26:19 +0000 Subject: [PATCH 04/25] Make synchronization works from FileEvent and ObserverService --- .../foundation/e/drive/EdriveApplication.java | 12 +- .../FileObservers/FileEventListener.java | 158 +++++++++++++++++- .../foundation/e/drive/database/DbHelper.java | 40 ++++- .../database/SyncedFileStateContract.java | 11 +- .../e/drive/database/SyncedFileStateDAO.java | 36 ++-- .../e/drive/database/SyncedFolderDAO.java | 13 ++ .../e/drive/models/SyncedFileState.java | 35 +++- .../e/drive/services/ObserverService.java | 62 +++---- .../services/SynchronizationService.java | 11 +- .../SynchronizationServiceConnection.java | 52 ++++++ 10 files changed, 353 insertions(+), 77 deletions(-) create mode 100644 app/src/main/java/foundation/e/drive/utils/SynchronizationServiceConnection.java diff --git a/app/src/main/java/foundation/e/drive/EdriveApplication.java b/app/src/main/java/foundation/e/drive/EdriveApplication.java index e8de2b00..e36b5752 100644 --- a/app/src/main/java/foundation/e/drive/EdriveApplication.java +++ b/app/src/main/java/foundation/e/drive/EdriveApplication.java @@ -31,17 +31,17 @@ public class EdriveApplication extends Application { private static final String TAG = "EdriveApplication"; private RecursiveFileObserver mFileObserver = null; - + private FileEventListener fileEventListener; @Override public void onCreate() { super.onCreate(); - + fileEventListener = new FileEventListener(getApplicationContext()); Log.i(TAG, "Starting"); resetOperationManagerSetting(); final String pathForObserver = Environment.getExternalStorageDirectory().getAbsolutePath(); - mFileObserver = new RecursiveFileObserver(getApplicationContext(), pathForObserver, new FileEventListener()); + mFileObserver = new RecursiveFileObserver(getApplicationContext(), pathForObserver, fileEventListener); SharedPreferences prefs = getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -77,8 +77,9 @@ public class EdriveApplication extends Application { */ public void startRecursiveFileObserver(){ if (!mFileObserver.isWatching()) { + fileEventListener.bindToSynchronizationService(); mFileObserver.startWatching(); - Log.d(TAG, "Starting RecursiveFileObserver on media's root folder"); + Log.d(TAG, "Starting RecursiveFileObserver on root folder"); } else { Log.w(TAG, "RecursiveFileObserver (for media's root folder) was already running"); @@ -87,7 +88,8 @@ public class EdriveApplication extends Application { public void stopRecursiveFileObserver(){ mFileObserver.stopWatching(); - Log.d(TAG, "RecursiveFileObserver on media's root folder stops watching "); + fileEventListener.unbindFromSynchronizationService(); + Log.d(TAG, "RecursiveFileObserver on root folder stops watching "); } @Override diff --git a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java index bcceca7b..a4a0b634 100644 --- a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java +++ b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java @@ -1,5 +1,6 @@ /* * Copyright © Narinder Rana (/e/ foundation). + * Copyright © Vincent Bourgmayer (/e/ foundation). * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at @@ -8,23 +9,170 @@ package foundation.e.drive.FileObservers; +import static foundation.e.drive.models.SyncRequest.Type.UPLOAD; + +import android.content.Context; +import android.content.Intent; import android.os.FileObserver; import android.util.Log; +import com.owncloud.android.lib.resources.files.FileUtils; + import java.io.File; +import foundation.e.drive.database.DbHelper; +import foundation.e.drive.models.SyncRequest; +import foundation.e.drive.models.SyncedFileState; +import foundation.e.drive.models.SyncedFolder; +import foundation.e.drive.services.SynchronizationService; +import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.SynchronizationServiceConnection; + /** * @author Narinder Rana * @author vincent Bourgmayer */ public class FileEventListener { - private final static String TAG = FileEventListener.class.getSimpleName(); + private static final String TAG = FileEventListener.class.getSimpleName(); + + private final Context appContext; + private final SynchronizationServiceConnection serviceConnection = new SynchronizationServiceConnection(); + + public FileEventListener(Context applicationContext) { + this.appContext = applicationContext; + } public void onEvent(int event, File file) { - if (event == FileObserver.CLOSE_WRITE) { //Event triggered after modification/creation - Log.d(TAG, "CLOSE_WRITE event for :" + file.getName()); - } else if (event == FileObserver.DELETE) { - Log.d(TAG, "DELETE event for :" + file.getName()); + if (event == FileObserver.DELETE) { + if (file.isDirectory()) { + handleDirectoryDelete(file); + } else { + handleFileDelete(file); + } + } else if (event == FileObserver.CLOSE_WRITE) { + + if (file.isDirectory()) { + handleDirectoryCloseWrite(file); + } else { + handleFileCloseWrite(file); + } + } else if (event == FileObserver.MOVE_SELF){ + Log.d(TAG, file.getAbsolutePath() + " has been moved. Not handled yet"); + } + } + + private void sendSyncRequestToSynchronizationService(SyncRequest request) { + Log.d(TAG, "Sending a SyncRequest for " + request.getSyncedFileState().getName()); + if (serviceConnection.isBoundToSynchronizationService()) { + serviceConnection.getSynchronizationService().queueOperation(request); + serviceConnection.getSynchronizationService().startSynchronization(); + }else{ + Log.w(TAG, "Impossible to send SyncRequest. FileEventListener is not bound to SynchronizationService"); + } + } + + private void handleDirectoryCloseWrite(File directory) { + final String fileLocalPath = CommonUtils.getLocalPath(directory); + Log.d(TAG, "handleDirectoryCloseWrite(" + fileLocalPath + ")"); + SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); + if (folder == null) { //it's a directory creation + final String parentPath = CommonUtils.getLocalPath(directory.getParentFile()); + SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); + if (parentFolder != null) { //if parent is in the DB + folder = new SyncedFolder(parentFolder, directory.getName() + FileUtils.PATH_SEPARATOR, directory.lastModified(), ""); + DbHelper.insertSyncedFolder(folder, appContext); + } + } else { //It's a directory update + folder.setLastModified(directory.lastModified()); + DbHelper.updateSyncedFolder(folder, appContext); + } + } + + private void handleDirectoryDelete(File directory) { + final String fileLocalPath = CommonUtils.getLocalPath(directory); + Log.d(TAG, "handleDirectoryDelete("+fileLocalPath+")"); + SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); + if (folder == null) { + //look for parent + final String parentPath = CommonUtils.getLocalPath(directory.getParentFile()); + SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); + if (parentFolder != null ) { //if parent is in the DB + folder = new SyncedFolder(parentFolder, directory.getName()+ FileUtils.PATH_SEPARATOR, directory.lastModified(), ""); + folder.setEnabled(false); + DbHelper.insertSyncedFolder(folder, appContext); + } + } else { //If already in DB + if (folder.isEnabled()) { + folder.setEnabled(false); + DbHelper.updateSyncedFolder(folder, appContext); + } } } + + private void handleFileCloseWrite(File file) { + final String fileLocalPath = CommonUtils.getLocalPath(file); + Log.d(TAG, "handleFileCloseWrite("+fileLocalPath+")"); + SyncRequest request = null; + SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); + + if (fileState == null) { //New file discovered + final String parentPath = CommonUtils.getLocalPath(file.getParentFile()); + SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); + if (parentFolder == null || !parentFolder.isEnabled()) { + Log.w(TAG, "Won't send sync request: no parent are known for new file: "+file.getName()); + return; + } + int scannableValue = 0; + if (parentFolder.isEnabled()) { + if (parentFolder.isScanRemote()) scannableValue++; + if (parentFolder.isScanLocal()) scannableValue += 2; + } + + final String remotePath = parentFolder.getRemoteFolder()+file.getName(); + fileState = new SyncedFileState(-1, file.getName(), CommonUtils.getLocalPath(file), remotePath, "", 0L, parentFolder.getId(), parentFolder.isMediaType(), scannableValue); + int storedId = DbHelper.manageSyncedFileStateDB(fileState, "INSERT", appContext); + if (storedId > 0) { + fileState.setId(storedId); + request = new SyncRequest(fileState, UPLOAD); + } else { + Log.w(TAG, "New File " + file.getName() + " observed but impossible to insert it in DB"); + } + } else { //File update + if (fileState.getScannable() > 1) { + request = new SyncRequest(fileState, UPLOAD); + } + } + if (request != null) { + sendSyncRequestToSynchronizationService(request); + } + } + + private void handleFileDelete(File file) { + final String fileLocalPath = CommonUtils.getLocalPath(file); + Log.d(TAG, "handleFileDelete("+fileLocalPath+")"); + SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); + if (fileState == null) { + Log.d(TAG, "Ignore event because file is not already in database"); + return; + } + + //If already in DB + if (fileState.getScannable() > 0) { + fileState.setScannable(0); + DbHelper.manageSyncedFileStateDB(fileState, "UPDATE", appContext); + } + } + + public void unbindFromSynchronizationService(){ + if(serviceConnection.isBoundToSynchronizationService()) + appContext.unbindService(serviceConnection); + else + Log.w(TAG, "Not bound to SynchronizationService: can't unbind."); + } + + public void bindToSynchronizationService(){ + Log.d(TAG, "bindToSynchronizationService()"); + final Intent SynchronizationServiceIntent = new Intent(appContext, SynchronizationService.class); + appContext.bindService(SynchronizationServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE); + } } diff --git a/app/src/main/java/foundation/e/drive/database/DbHelper.java b/app/src/main/java/foundation/e/drive/database/DbHelper.java index d1f94ee7..383c5fdb 100644 --- a/app/src/main/java/foundation/e/drive/database/DbHelper.java +++ b/app/src/main/java/foundation/e/drive/database/DbHelper.java @@ -24,7 +24,7 @@ import foundation.e.drive.models.SyncedFileState; */ public final class DbHelper extends SQLiteOpenHelper { final private static String TAG = DbHelper.class.getSimpleName(); //Tag for log - private static final int DATABASE_VERSION = 19; //20/09/2018 + private static final int DATABASE_VERSION = 20; //16/03/2022 public static final String DATABASE_NAME = "eelo_drive.db"; /** @@ -53,18 +53,22 @@ public final class DbHelper extends SQLiteOpenHelper { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.i(TAG, "onUpgrade(db, "+oldVersion+", "+newVersion+")"); - if(oldVersion < 19){ - try { + try { + if (oldVersion < 19) { db.execSQL(SyncedFolderContract.UPDATE_TABLE_TO_VERSION_19); db.execSQL(SyncedFileStateContract.UPDATE_TABLE_TO_VERSION_19); - db.execSQL(SyncedFileStateContract.UPDATE_MEDIA_DATA_TO_VERSION_19); db.execSQL(SyncedFileStateContract.UPDATE_SETTINGS_DATA_TO_VERSION_19); db.execSQL(SyncedFolderContract.UPDATE_MEDIA_DATA_TO_VERSION_19); db.execSQL(SyncedFolderContract.UPDATE_SETTINGS_DATA_TO_VERSION_19); - }catch(Exception e){ - Log.e(TAG, toString()); } + if (oldVersion < 20) { + db.execSQL(SyncedFileStateContract.UPDATE_TABLE_TO_VERSION_20); + db.execSQL(SyncedFileStateContract.UPDATE_MEDIA_DATA_TO_VERSION_20); + db.execSQL(SyncedFileStateContract.UPDATE_SETTINGS_DATA_TO_VERSION_20); + } + } catch(Exception e) { + Log.e(TAG, toString()); } } @@ -116,7 +120,6 @@ public final class DbHelper extends SQLiteOpenHelper { } dao.close(); } - return result; } @@ -178,6 +181,19 @@ public final class DbHelper extends SQLiteOpenHelper { return result; } + + public static int updateSyncedFolder(SyncedFolder syncedFolder, Context context) { + int result = -1; + //Connect to DB + SyncedFolderDAO dao = openSyncedFolderDAO(context, true); + if (dao == null){ + return result; + } + result = dao.update( syncedFolder ); + dao.close(); + return result; + } + /** * Load SyncedFolder's from DB * @param context app or service activity @@ -243,6 +259,16 @@ public final class DbHelper extends SQLiteOpenHelper { return result; } + public static SyncedFolder getSyncedFolderByLocalPath(String localPath, Context context){ + SyncedFolderDAO dao = openSyncedFolderDAO(context, true); + if (dao == null) { + return null; + } + SyncedFolder syncedFolder = dao.getSyncedFolderByLocalPath(localPath); + dao.close(); + return syncedFolder; + } + /** * Set the lastModified value of SyncedFolder to 1. * The goal is to force to rescan it next time. diff --git a/app/src/main/java/foundation/e/drive/database/SyncedFileStateContract.java b/app/src/main/java/foundation/e/drive/database/SyncedFileStateContract.java index 3d0f69cf..2b91c8c3 100644 --- a/app/src/main/java/foundation/e/drive/database/SyncedFileStateContract.java +++ b/app/src/main/java/foundation/e/drive/database/SyncedFileStateContract.java @@ -25,6 +25,7 @@ class SyncedFileStateContract implements BaseColumns{ static final String LOCAL_LAST_MODIFIED = "local_last_modified"; static final String SYNCEDFOLDER_ID = "synced_folder_id"; static final String IS_MEDIA_TYPE = "is_media_type"; + static final String SCANNABLE = "scannable"; static final String SQL_CREATE_TABLE_SYNCEDFILESTATE = "CREATE TABLE "+TABLE_NAME+" ( " @@ -36,6 +37,7 @@ class SyncedFileStateContract implements BaseColumns{ +LOCAL_LAST_MODIFIED+" INTEGER, " + SYNCEDFOLDER_ID +" INTEGER, " +IS_MEDIA_TYPE+" BOOLEAN," + +SCANNABLE+" INTEGER, " +"CONSTRAINT synced_unicity_constraint UNIQUE (" +FILE_NAME+", " +LOCAL_PATH+", " @@ -45,8 +47,8 @@ class SyncedFileStateContract implements BaseColumns{ static final String SQL_DELETE_TABLE_SYNCEDFILESTATE = " DROP TABLE IF EXISTS " + TABLE_NAME; + //Update for version 18 and lower static final String UPDATE_TABLE_TO_VERSION_19 = "ALTER TABLE "+TABLE_NAME+" ADD COLUMN "+IS_MEDIA_TYPE+" BOOLEAN;"; - static final String UPDATE_MEDIA_DATA_TO_VERSION_19 = "UPDATE "+TABLE_NAME+ " SET "+IS_MEDIA_TYPE+" = 1 WHERE "+ REMOTE_PATH+" LIKE \"/Photos/%\" OR "+ @@ -60,4 +62,11 @@ class SyncedFileStateContract implements BaseColumns{ static final String UPDATE_SETTINGS_DATA_TO_VERSION_19 = "UPDATE "+TABLE_NAME+ " SET "+IS_MEDIA_TYPE+" = 0 WHERE "+REMOTE_PATH+" LIKE \"/Devices/%\";"; + //update for version 19 and lower + static final String UPDATE_TABLE_TO_VERSION_20 = "ALTER TABLE "+TABLE_NAME+" ADD COLUMN "+SCANNABLE+" INTEGER;"; + static final String UPDATE_MEDIA_DATA_TO_VERSION_20 = "UPDATE "+TABLE_NAME+" SET "+SCANNABLE+" = 3 WHERE" + + IS_MEDIA_TYPE+" = 1 ;" ; + static final String UPDATE_SETTINGS_DATA_TO_VERSION_20 = "UPDATE "+TABLE_NAME+" SET "+SCANNABLE+" = 2 WHERE" + + IS_MEDIA_TYPE+" = 0 ;" ; + } diff --git a/app/src/main/java/foundation/e/drive/database/SyncedFileStateDAO.java b/app/src/main/java/foundation/e/drive/database/SyncedFileStateDAO.java index 91865813..9864b9de 100644 --- a/app/src/main/java/foundation/e/drive/database/SyncedFileStateDAO.java +++ b/app/src/main/java/foundation/e/drive/database/SyncedFileStateDAO.java @@ -20,6 +20,8 @@ import java.util.ArrayList; import java.util.List; import foundation.e.drive.models.SyncedFileState; + +import static foundation.e.drive.database.SyncedFileStateContract.SCANNABLE; import static foundation.e.drive.database.SyncedFileStateContract.TABLE_NAME; import static foundation.e.drive.database.SyncedFileStateContract.FILE_NAME; import static foundation.e.drive.database.SyncedFileStateContract.IS_MEDIA_TYPE; @@ -39,16 +41,6 @@ class SyncedFileStateDAO { private SQLiteDatabase mDB; private final DbHelper mHelper; - /*private final String[] allColumns = { SyncedFileStateContract._ID, - SyncedFileStateContract.FILE_NAME, - SyncedFileStateContract.LOCAL_PATH, - SyncedFileStateContract.REMOTE_PATH, - SyncedFileStateContract.LAST_ETAG, - - SyncedFileStateContract.LOCAL_LAST_MODIFIED, - SyncedFileStateContract.SYNCEDFOLDER_ID, - SyncedFileStateContract.IS_MEDIA_TYPE - };*/ SyncedFileStateDAO(Context context){ this.mHelper = new DbHelper(context); @@ -75,7 +67,7 @@ class SyncedFileStateDAO { * @param syncedFileState syncedFileState to convert * @return a ContentValues object */ - private ContentValues toContentValues(SyncedFileState syncedFileState){ + private ContentValues toContentValues(SyncedFileState syncedFileState) { ContentValues values = new ContentValues(); values.put( FILE_NAME, syncedFileState.getName() ); values.put( LOCAL_PATH, syncedFileState.getLocalPath() ); @@ -84,6 +76,7 @@ class SyncedFileStateDAO { values.put( LOCAL_LAST_MODIFIED, syncedFileState.getLocalLastModified() ); values.put( SYNCEDFOLDER_ID, syncedFileState.getSyncedFolderId() ); values.put( IS_MEDIA_TYPE, syncedFileState.isMediaType() ? 1 : 0 ); + values.put( SCANNABLE, syncedFileState.getScannable()); return values; } @@ -142,7 +135,7 @@ class SyncedFileStateDAO { * @param syncedFolderids List of path to filter. Need to be directory path * @return List List of SyncedFileState filtered on syncedFolder ID. */ - List getBySyncedFolderID(List syncedFolderids){ + List getBySyncedFolderID(List syncedFolderids) { String query = "Select " +SyncedFileStateContract._ID+", " +FILE_NAME+", " @@ -151,10 +144,11 @@ class SyncedFileStateDAO { +LAST_ETAG+", " +LOCAL_LAST_MODIFIED+", " + SYNCEDFOLDER_ID+", " - + IS_MEDIA_TYPE + + IS_MEDIA_TYPE+", " + + SCANNABLE +" FROM " +TABLE_NAME; - if(syncedFolderids.size() > 0) { + if (syncedFolderids.size() > 0) { query+=" WHERE "; for (int i = -1, idsSize = syncedFolderids.size(); ++i < idsSize; ) { @@ -181,8 +175,8 @@ class SyncedFileStateDAO { * @param path local path or remote path * @return SyncedFileState obtain by the query or null if none has been found */ - SyncedFileState getByPath(String path, boolean isLocalPath){ - String query = "Select " + SyncedFileState getByPath(String path, boolean isLocalPath) { + String query = "Select " +SyncedFileStateContract._ID+", " +FILE_NAME+", " +LOCAL_PATH+", " @@ -190,10 +184,11 @@ class SyncedFileStateDAO { +LAST_ETAG+", " +LOCAL_LAST_MODIFIED+", " + SYNCEDFOLDER_ID+", " - + IS_MEDIA_TYPE+ + + IS_MEDIA_TYPE+", " + + SCANNABLE+ " FROM " +TABLE_NAME+" WHERE "; - if(isLocalPath) + if (isLocalPath) query+=LOCAL_PATH ; else query+=REMOTE_PATH ; @@ -203,7 +198,7 @@ class SyncedFileStateDAO { Cursor cursor = mDB.rawQuery(query, null); cursor.moveToFirst(); SyncedFileState syncedFileState = null; - if( !cursor.isAfterLast()) { + if ( !cursor.isAfterLast()) { syncedFileState = cursorToSyncedFileState(cursor); } cursor.close(); @@ -224,7 +219,8 @@ class SyncedFileStateDAO { cursor.getString(4 ),// last Etag cursor.getLong(5 ),//Local last modified cursor.getLong(6 ), //SyncedFolderID - (cursor.getInt(7) == 1 ) //is Media Type + (cursor.getInt(7) == 1), //is Media Type + cursor.getInt(8) //scannable ); } } diff --git a/app/src/main/java/foundation/e/drive/database/SyncedFolderDAO.java b/app/src/main/java/foundation/e/drive/database/SyncedFolderDAO.java index 8af54a1f..c70be5e5 100644 --- a/app/src/main/java/foundation/e/drive/database/SyncedFolderDAO.java +++ b/app/src/main/java/foundation/e/drive/database/SyncedFolderDAO.java @@ -29,6 +29,8 @@ import static foundation.e.drive.database.SyncedFolderContract.SCANREMOTE; import static foundation.e.drive.database.SyncedFolderContract.IS_MEDIA_TYPE; import static foundation.e.drive.database.SyncedFolderContract.TABLE_NAME; +import com.owncloud.android.lib.resources.files.FileUtils; + /** * @author Vincent Bourgmayer * Source: https://vogella.developpez.com/tutoriels/android/utilisation-base-donnees-sqlite/ @@ -174,6 +176,17 @@ class SyncedFolderDAO { return result; } + SyncedFolder getSyncedFolderByLocalPath(String localPath){ + Cursor cursor = mDB.query(TABLE_NAME, allColumns, LOCAL_PATH+" like \""+localPath+ FileUtils.PATH_SEPARATOR+"\"", new String[0], null, null, null); + cursor.moveToFirst(); + SyncedFolder result = null; + if ( !cursor.isAfterLast() ) { + result = cursorToSyncedFolder(cursor); + } + cursor.close(); + return result; + } + /** * Create a syncedFolder object from data in cursor. * @param cursor cursor containing data to build syncedFolder diff --git a/app/src/main/java/foundation/e/drive/models/SyncedFileState.java b/app/src/main/java/foundation/e/drive/models/SyncedFileState.java index 42c76a10..f3b842b3 100644 --- a/app/src/main/java/foundation/e/drive/models/SyncedFileState.java +++ b/app/src/main/java/foundation/e/drive/models/SyncedFileState.java @@ -17,7 +17,10 @@ import android.os.Parcelable; */ public class SyncedFileState implements Parcelable { - + public static final int NOT_SCANNABLE=0; + public static final int ECLOUD_SCANNABLE=1; + public static final int DEVICE_SCANNABLE=2; + public static final int ALL_SCANNABLE=3; protected SyncedFileState(){}; //@ToRemove. Test Only. It's to allow to make a mock SyncedFileState Class in test. private int id; @@ -27,7 +30,8 @@ public class SyncedFileState implements Parcelable { private String lastETAG; //Last got Etag for the file private long localLastModified; private long syncedFolderId; - private boolean isMediaType; //if true this is a media synchronisable else it it is a settings synchronisable. + private boolean isMediaType; // true if this is a media synchronisable else it is a settings synchronisable. + private int scannable; /** * Full constructor @@ -39,7 +43,7 @@ public class SyncedFileState implements Parcelable { * @param lastModified Last modified time where local file has changed * @param isMediaType true if its sync as medias or as device/app' settings. */ - public SyncedFileState(int id, String name, String localPath, String remotePath, String etag, long lastModified, long syncedFolderId, boolean isMediaType){ + public SyncedFileState(int id, String name, String localPath, String remotePath, String etag, long lastModified, long syncedFolderId, boolean isMediaType, int scannable){ this.id = id; this.name = name; this.localPath = localPath; @@ -48,6 +52,7 @@ public class SyncedFileState implements Parcelable { this.localLastModified = lastModified; this.syncedFolderId = syncedFolderId; this.isMediaType = isMediaType; + this.scannable = scannable; } protected SyncedFileState(Parcel in) { @@ -59,6 +64,7 @@ public class SyncedFileState implements Parcelable { localLastModified = in.readLong(); syncedFolderId = in.readLong(); isMediaType = in.readByte() != 0; + scannable = in.readInt(); } @Override @@ -71,6 +77,7 @@ public class SyncedFileState implements Parcelable { dest.writeLong(localLastModified); dest.writeLong(syncedFolderId); dest.writeByte((byte) (isMediaType ? 1 : 0)); + dest.writeInt(scannable); } public static final Creator CREATOR = new Creator() { @@ -152,6 +159,24 @@ public class SyncedFileState implements Parcelable { return isMediaType; } + + /** + * Return in which context the file can scan + * @return 0: not scannable. 1: scannable on ecloud. 2: scannable on device. 3: scannable everywhere + */ + public int getScannable() { + return scannable; + } + + /** + * Define in which context the file can be scan + * @param scannable 0: never. 1: on cloud only. 2: on device only. 3: in every context. + */ + public void setScannable(int scannable) { + this.scannable = scannable; + } + + @Override public String toString(){ return "SyncedFileState :" @@ -162,10 +187,10 @@ public class SyncedFileState implements Parcelable { +"\nLast Etag: "+this.lastETAG +"\nLocal last modified: "+this.localLastModified +"\nSyncedFolderId: "+this.syncedFolderId - +"\nisMediaType: "+this.isMediaType; + +"\nisMediaType: "+this.isMediaType + +"\nscannable: "+this.scannable; } - @Override public int describeContents() { return 0; 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 632d5b8a..a84cb696 100644 --- a/app/src/main/java/foundation/e/drive/services/ObserverService.java +++ b/app/src/main/java/foundation/e/drive/services/ObserverService.java @@ -50,6 +50,7 @@ import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; import foundation.e.drive.utils.ServiceExceptionHandler; +import foundation.e.drive.utils.SynchronizationServiceConnection; import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; import static foundation.e.drive.utils.AppConstants.INITIALIZATION_HAS_BEEN_DONE; @@ -70,36 +71,24 @@ public class ObserverService extends Service implements OnRemoteOperationListene private int initialFolderCounter; private Account mAccount; private HashMap syncRequests; //integer is SyncedFileState id; Parcelable is the operation - - private SynchronizationService synchronizationService; - private boolean boundToSynchronizationService = false; - private ServiceConnection SynchronizationServiceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - SynchronizationService.SynchronizationBinder binder = (SynchronizationService.SynchronizationBinder) iBinder; - synchronizationService = binder.getService(); - boundToSynchronizationService = true; - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - Log.e(TAG, "onServiceDisconnected"); - boundToSynchronizationService = false; - } - }; + private SynchronizationServiceConnection synchronizationServiceConnection = new SynchronizationServiceConnection(); /* Lifecycle Methods */ @Override public void onDestroy(){ Log.i(TAG, "onDestroy()"); + unbindService(synchronizationServiceConnection); super.onDestroy(); this.mSyncedFolders = null; } + @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand("+startId+")"); + final Intent SynchronizationServiceIntent = new Intent(this, SynchronizationService.class); + bindService(SynchronizationServiceIntent, synchronizationServiceConnection, Context.BIND_AUTO_CREATE); CommonUtils.setServiceUnCaughtExceptionHandler(this); @@ -162,11 +151,8 @@ public class ObserverService extends Service implements OnRemoteOperationListene } this.syncRequests = new HashMap<>(); - Intent SynchronizationServiceIntent = new Intent(this.getApplicationContext(), SynchronizationService.class); - bindService(SynchronizationServiceIntent, SynchronizationServiceConnection, Context.BIND_AUTO_CREATE); - begin(); - return super.onStartCommand( intent, flags, startId ); + return START_NOT_STICKY; } /* Common methods */ @@ -317,11 +303,11 @@ public class ObserverService extends Service implements OnRemoteOperationListene } this.startScan(false); - Log.v(TAG, "operationsForIntent contains "+ syncRequests.size() ); + 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()) { - this.synchronizationService.queueOperations(syncRequests.values()); + passSyncRequestsToSynchronizationService(); } else { Log.w(TAG, "There is no file to sync."); getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) @@ -330,13 +316,22 @@ public class ObserverService extends Service implements OnRemoteOperationListene .putBoolean(AppConstants.KEY_OMS_IS_WORKING, false) .apply(); } - this.isWorking = false; - unbindService(SynchronizationServiceConnection); this.stopSelf(); + } + } + + private void passSyncRequestsToSynchronizationService() { + if (synchronizationServiceConnection.isBoundToSynchronizationService()) { + synchronizationServiceConnection.getSynchronizationService().queueOperations(syncRequests.values()); + synchronizationServiceConnection.getSynchronizationService().startSynchronization(); + } else { + Log.e(TAG, "ERROR: impossible to bind ObserverService to SynchronizationService"); } } + + /** * Method to get Id of SyncedFolder to scan * @return List id of SyncedFolder to scan @@ -431,8 +426,13 @@ public class ObserverService extends Service implements OnRemoteOperationListene String fileName = CommonUtils.getFileNameFromPath(remoteFilePath); //get remote file's name if (fileName != null) { + int scannableValue = 0; + if (parentFolder.isEnabled()) { + if (parentFolder.isScanRemote()) scannableValue++; + if (parentFolder.isScanLocal()) scannableValue += 2; + } //create syncedFileState - SyncedFileState newRemoteFile = new SyncedFileState(-1, fileName, parentFolder.getLocalFolder() + fileName, remoteFilePath, remoteFile.getEtag(), 0, parentFolder.getId(), parentFolder.isMediaType()); + SyncedFileState newRemoteFile = new SyncedFileState(-1, fileName, parentFolder.getLocalFolder() + fileName, remoteFilePath, remoteFile.getEtag(), 0, parentFolder.getId(), parentFolder.isMediaType(), scannableValue); //Store it in DB int storedId = DbHelper.manageSyncedFileStateDB(newRemoteFile, "INSERT", this); @@ -631,8 +631,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene } } } //end of iterator loop - - if (contentToSyncFound) { DbHelper.updateSyncedFolders(mSyncedFolders, this); //@ToDo: maybe do this when all contents will be synced. List syncedFileStates = DbHelper.getSyncedFileStatesByFolders(this, @@ -706,8 +704,14 @@ public class ObserverService extends Service implements OnRemoteOperationListene //look into synced folders if folder path exist for(SyncedFolder syncedFolder : mSyncedFolders){ if (syncedFolder.getLocalFolder().equals(parentPath)){ + int scannableValue = 0; + if (syncedFolder.isEnabled()) { + if (syncedFolder.isScanRemote()) scannableValue++; + if (syncedFolder.isScanLocal()) scannableValue += 2; + } + //create the syncedFile State - SyncedFileState newSyncedFileState = new SyncedFileState(-1, localFile.getName(), filePath, syncedFolder.getRemoteFolder() + localFile.getName(), "", 0, syncedFolder.getId(), syncedFolder.isMediaType()); + SyncedFileState newSyncedFileState = new SyncedFileState(-1, localFile.getName(), filePath, syncedFolder.getRemoteFolder() + localFile.getName(), "", 0, syncedFolder.getId(), syncedFolder.isMediaType(),scannableValue); //Store it in DB int storedId = DbHelper.manageSyncedFileStateDB(newSyncedFileState, "INSERT", this); 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 d532786e..e03d4a55 100644 --- a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java +++ b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java @@ -66,16 +66,17 @@ public class SynchronizationService extends Service implements OnRemoteOperation public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand()"); - final SharedPreferences prefs = getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences prefs = this.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, ""); + account = CommonUtils.getAccount(accountName, accountType, AccountManager.get(this)); - if (prefs.getString(AccountManager.KEY_ACCOUNT_NAME, null) == null) { + if (account == null) { Log.w(TAG, "No account available. Stop SynchronizationService"); stopSelf(); return START_NOT_STICKY; } - - account = (Account) intent.getParcelableExtra("account"); syncedRequestQueue = new ConcurrentLinkedDeque<>(); startedOperations = new Hashtable<>(); threadPool = new Thread[workerAmount]; @@ -259,7 +260,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation } public class SynchronizationBinder extends Binder{ - SynchronizationService getService(){ + public SynchronizationService getService(){ return SynchronizationService.this; } } diff --git a/app/src/main/java/foundation/e/drive/utils/SynchronizationServiceConnection.java b/app/src/main/java/foundation/e/drive/utils/SynchronizationServiceConnection.java new file mode 100644 index 00000000..7f828ff9 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/utils/SynchronizationServiceConnection.java @@ -0,0 +1,52 @@ +/** + * Copyright © Vincent Bourgmayer (/e/ foundation). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package foundation.e.drive.utils; + +import android.content.ComponentName; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.util.Log; + +import foundation.e.drive.services.SynchronizationService; + +/** + * @author Vincent Bourgmayer + */ +public class SynchronizationServiceConnection implements ServiceConnection { + private final static String TAG = SynchronizationServiceConnection.class.getSimpleName(); + + private SynchronizationService synchronizationService; + private boolean boundToSynchronizationService = false; + + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + Log.i(TAG, "onServiceConnected: binding to SynchronizationService"); + SynchronizationService.SynchronizationBinder binder = (SynchronizationService.SynchronizationBinder) iBinder; + synchronizationService = binder.getService(); + boundToSynchronizationService = true; + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + Log.i(TAG, "onServiceDisconnected: unbinding from SynchronizationService"); + boundToSynchronizationService = false; + } + + public boolean isBoundToSynchronizationService() { + return boundToSynchronizationService; + } + + /** + * Get SynchronizationService. Might be null! + * @return + */ + public SynchronizationService getSynchronizationService() { + return synchronizationService; + } +} -- GitLab From da7182ca7804981625026103cb16828144162d7c Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 21 Mar 2022 13:12:04 +0100 Subject: [PATCH 05/25] - Refactor ResetService: split "onCreate()" content into private method - Remove duplicated code to stop Worker - Fix coding style for space in if/else statement --- .../e/drive/services/ResetService.java | 100 +++++++++--------- 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/services/ResetService.java b/app/src/main/java/foundation/e/drive/services/ResetService.java index 99aad5fe..395e2acb 100644 --- a/app/src/main/java/foundation/e/drive/services/ResetService.java +++ b/app/src/main/java/foundation/e/drive/services/ResetService.java @@ -39,64 +39,25 @@ public class ResetService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand()"); - if( intent.getExtras() != null ) { - String intent_accountName = intent.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME, ""); - String intent_accountType = intent.getExtras().getString(AccountManager.KEY_ACCOUNT_TYPE, ""); + if ( intent.getExtras() != null ) { + final String intent_accountName = intent.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME, ""); + final String intent_accountType = intent.getExtras().getString(AccountManager.KEY_ACCOUNT_TYPE, ""); - if(!intent_accountName.isEmpty() && !intent_accountType.isEmpty()){ - SharedPreferences prefs = this.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); - - if(intent_accountName.equals(prefs.getString(AccountManager.KEY_ACCOUNT_NAME, "") ) - && intent_accountType.equals(prefs.getString(AccountManager.KEY_ACCOUNT_TYPE, "") ) ){ - - //1. Stop sync services - - Intent stopperIntent = new Intent(getApplicationContext(), ObserverService.class); - boolean result = getApplicationContext().stopService( stopperIntent ); - Log.d(TAG, "stop ObserverService: "+result); - - stopperIntent = new Intent(getApplicationContext(), InitializerService.class); - result = getApplicationContext().stopService( stopperIntent ); - Log.d(TAG, "stop InitializerService: "+result); - - stopperIntent = new Intent(getApplicationContext(), SynchronizationService.class); //@todo try to replace it by stopperIntent.setClassName(getApplicationContext, OperationManagerService.class) - result = getApplicationContext().stopService( stopperIntent ); - - Log.d(TAG, "stop SynchronizationService: "+result); + if (!intent_accountName.isEmpty() && !intent_accountType.isEmpty()) { + final SharedPreferences prefs = this.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + if (intent_accountName.equals(prefs.getString(AccountManager.KEY_ACCOUNT_NAME, "")) + && intent_accountType.equals(prefs.getString(AccountManager.KEY_ACCOUNT_TYPE, ""))) { WorkManager.getInstance(this).cancelAllWorkByTag(AppConstants.WORK_GENERIC_TAG); - ( (EdriveApplication) this.getApplication() ).stopRecursiveFileObserver(); - WorkManager.getInstance(this).cancelAllWorkByTag(AppConstants.WORK_GENERIC_TAG); - - ( (EdriveApplication) this.getApplication() ).stopRecursiveFileObserver(); + stopAllServices(); - //3. delete DB - result = this.deleteDatabase(DbHelper.DATABASE_NAME); + boolean result = deleteDatabase(DbHelper.DATABASE_NAME); Log.d(TAG, "Remove Database: "+result); - //4. preferences cleaning - - //Remove the prefs file. - if(!this.deleteSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME)){ - - //If removal failed, clear all data inside - prefs.edit().remove(AccountManager.KEY_ACCOUNT_NAME) - .remove(AccountManager.KEY_ACCOUNT_TYPE) - .putBoolean(INITIALIZATION_HAS_BEEN_DONE, false) - .remove(INITIALFOLDERS_NUMBER) - .remove(AppConstants.KEY_OMS_IS_WORKING) - .remove(AppConstants.KEY_LAST_SYNC_TIME) - .apply(); - } - - - //5. Remove Cached File - File[] cachedFiles = this.getApplicationContext().getExternalCacheDir().listFiles(); - for(File f : cachedFiles){ - f.delete(); - } + cleanSharedPreferences(prefs); + removeCachedFiles(); } } } @@ -104,9 +65,46 @@ public class ResetService extends Service { return super.onStartCommand(intent, flags, startId); } + + private void stopAllServices() { + Intent stopperIntent = new Intent(getApplicationContext(), ObserverService.class); + boolean result = getApplicationContext().stopService( stopperIntent ); + Log.d(TAG, "stop ObserverService: "+result); + + stopperIntent = new Intent(getApplicationContext(), InitializerService.class); + result = getApplicationContext().stopService( stopperIntent ); + Log.d(TAG, "stop InitializerService: "+result); + + stopperIntent = new Intent(getApplicationContext(), SynchronizationService.class); + result = getApplicationContext().stopService( stopperIntent ); + + Log.d(TAG, "stop SynchronizationService: "+result); + } + + private void cleanSharedPreferences(SharedPreferences prefs) { + if (!this.deleteSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME)) { + + //If removal failed, clear all data inside + prefs.edit().remove(AccountManager.KEY_ACCOUNT_NAME) + .remove(AccountManager.KEY_ACCOUNT_TYPE) + .putBoolean(INITIALIZATION_HAS_BEEN_DONE, false) + .remove(INITIALFOLDERS_NUMBER) + .remove(AppConstants.KEY_OMS_IS_WORKING) + .remove(AppConstants.KEY_LAST_SYNC_TIME) + .apply(); + } + } + + private void removeCachedFiles() { + File[] cachedFiles = this.getApplicationContext().getExternalCacheDir().listFiles(); + for (File f : cachedFiles) { + f.delete(); + } + } + @Nullable @Override public IBinder onBind(Intent intent) { return null; } -} +} \ No newline at end of file -- GitLab From 5d65ee2bb2cb24e607475de9e83b5e316f73313e Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Wed, 23 Mar 2022 08:18:04 +0000 Subject: [PATCH 06/25] clean UploadFileOperation.java --- .../drive/operations/UploadFileOperation.java | 153 +++++++----------- .../services/SynchronizationService.java | 2 +- 2 files changed, 57 insertions(+), 98 deletions(-) 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 f4ec50ae..e0fdfe24 100644 --- a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java @@ -9,8 +9,6 @@ package foundation.e.drive.operations; import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; import android.util.Log; import androidx.annotation.VisibleForTesting; @@ -34,51 +32,25 @@ import foundation.e.drive.utils.CommonUtils; * @author Vincent Bourgmayer * High level Operation which upload a local file to a remote cloud storage */ -public class UploadFileOperation extends RemoteOperation implements Parcelable { +public class UploadFileOperation extends RemoteOperation { private final static String TAG = UploadFileOperation.class.getSimpleName(); - private int restartCounter =0; private long previousLastModified; //get to restore real value if all trials fails - private Context mContext; - private SyncedFileState mSyncedState; - + private Context context; + private SyncedFileState syncedState; private long availableQuota = -1; - protected UploadFileOperation(Parcel in) { - restartCounter = in.readInt(); - previousLastModified = in.readLong(); - mSyncedState = in.readParcelable(SyncedFileState.class.getClassLoader()); - availableQuota = in.readLong(); - } - - public static final Creator CREATOR = new Creator() { - @Override - public UploadFileOperation createFromParcel(Parcel in) { - return new UploadFileOperation(in); - } - - @Override - public UploadFileOperation[] newArray(int size) { - return new UploadFileOperation[size]; - } - }; - /** * Construct an upload operation with an already known syncedFileState * @param syncedFileState syncedFileState corresponding to file. */ - public UploadFileOperation (SyncedFileState syncedFileState){ - this.mSyncedState = syncedFileState; - this.previousLastModified = mSyncedState.getLocalLastModified(); - } - - public void setContext(Context context){ - this.mContext = context; + public UploadFileOperation (SyncedFileState syncedFileState, Context context) { + this.syncedState = syncedFileState; + this.previousLastModified = syncedState.getLocalLastModified(); + this.context = context; } - - /** * Execute the operation: * @@ -91,99 +63,99 @@ public class UploadFileOperation extends RemoteOperation implements Parcelable { * hasn't change since last update or "forbidden" if no remote path can be fetch. */ @Override - protected RemoteOperationResult run( OwnCloudClient client ) { + protected RemoteOperationResult run(OwnCloudClient client ) { //as operation isn't executed immediatly, file might have been deleted since creation of operation - if(mSyncedState == null ){ + 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(mSyncedState.getLocalPath()); - if(file == null || !file.exists()){ + 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); } - String targetPath = mSyncedState.getRemotePath(); + final String targetPath = syncedState.getRemotePath(); //If an Etag is already Stored and LastModified from DB is the same as real file - if (mSyncedState.isLastEtagStored() - && mSyncedState.getLocalLastModified() == file.lastModified()) { - Log.d(TAG, "mySyncedState last modified: "+mSyncedState.getLocalLastModified()+" <=> mFile last modified: "+file.lastModified() +": So return sync_conflict"); + 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); } - if(this.availableQuota == -1){ + if (this.availableQuota == -1) { RemoteOperationResult checkQuotaResult = checkAvailableSpace(client, file.length()); - if( checkQuotaResult.getCode() != ResultCode.OK ){ + if (checkQuotaResult.getCode() != ResultCode.OK) { return new RemoteOperationResult(checkQuotaResult.getCode()); } } - UploadFileRemoteOperation uploadOperation = buildUploadOperation(file, targetPath); + final UploadFileRemoteOperation uploadOperation = buildUploadOperation(file, targetPath); // Execute UploadFileOperation - RemoteOperationResult uploadResult = uploadOperation.execute( client ); - ResultCode mResultCode; + RemoteOperationResult uploadResult = uploadOperation.execute(client ); + ResultCode resultCode; boolean mustRestart = true; //if upload is a success - if( uploadResult.isSuccess() ){ + if (uploadResult.isSuccess()) { Object data = uploadResult.getSingleData(); - if(data != null){ - mSyncedState.setLastETAG((String) data); + if (data != null) { + syncedState.setLastETAG((String) data); } - mSyncedState.setLocalLastModified(file.lastModified()); - mResultCode = uploadResult.getCode(); + syncedState.setLocalLastModified(file.lastModified()); + resultCode = uploadResult.getCode(); mustRestart = false; - }else{ + } else { //Si les répértoires ou mettre le fichier n'existe pas, on les ajoutes. - if( uploadResult.getCode() == ResultCode.FILE_NOT_FOUND ){ + if (uploadResult.getCode() == ResultCode.FILE_NOT_FOUND ) { + resultCode = ResultCode.FILE_NOT_FOUND; Log.d(TAG, "Catched a File not found result for : "+file.getName()+", create missing remote path then retry"); - String remoteFoldersPath = targetPath.substring( 0, targetPath.lastIndexOf(FileUtils.PATH_SEPARATOR)+1 ); - mResultCode = ResultCode.FILE_NOT_FOUND; - CreateFolderRemoteOperation createFolderOperation = new CreateFolderRemoteOperation( remoteFoldersPath, true ); + final String remoteFolderPath = targetPath.substring(0, targetPath.lastIndexOf(FileUtils.PATH_SEPARATOR)+1 ); + final CreateFolderRemoteOperation createFolderOperation = new CreateFolderRemoteOperation(remoteFolderPath, true ); try{ - RemoteOperationResult createFolderResult = createFolderOperation.execute( client ); + RemoteOperationResult createFolderResult = createFolderOperation.execute(client ); - if(!createFolderResult.isSuccess() && createFolderResult.getCode() != ResultCode.FOLDER_ALREADY_EXISTS){ - mResultCode = createFolderResult.getCode(); + if (!createFolderResult.isSuccess() && createFolderResult.getCode() != ResultCode.FOLDER_ALREADY_EXISTS) { + resultCode = createFolderResult.getCode(); Log.e(TAG, createFolderResult.getLogMessage()); mustRestart = false; - mSyncedState.setLocalLastModified( this.previousLastModified); + syncedState.setLocalLastModified(this.previousLastModified); } - }catch(Exception e){ + }catch(Exception e) { Log.e(TAG, e.toString() ); - mSyncedState.setLocalLastModified( this.previousLastModified); + syncedState.setLocalLastModified(this.previousLastModified); mustRestart = false; } - }else if(uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED){ - mResultCode = ResultCode.QUOTA_EXCEEDED; + } else if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { + resultCode = ResultCode.QUOTA_EXCEEDED; mustRestart = false; - }else{ + } else { //Upload failed Log.e(TAG, "UploadFileRemoteOperation for : " + file.getName() + " failed => code: " + uploadResult.getCode()); - mResultCode = ResultCode.UNKNOWN_ERROR; + resultCode = ResultCode.UNKNOWN_ERROR; mustRestart = false; } } - if(mustRestart) { + if (mustRestart) { if (this.restartCounter < 1) { this.restartCounter += 1; //if we encounter more than three times same error, stop trying to download. return this.run(client); } else { - mSyncedState.setLocalLastModified( this.previousLastModified); //Revert syncFileState to its previous state + syncedState.setLocalLastModified(this.previousLastModified); //Revert syncFileState to its previous state } } // updated syncedFile in DB - DbHelper.manageSyncedFileStateDB(mSyncedState, "UPDATE", mContext); + DbHelper.manageSyncedFileStateDB(syncedState, "UPDATE", context); ArrayList datas = new ArrayList<>(); - datas.add(mSyncedState.getSyncedFolderId()); - final RemoteOperationResult finalResult = new RemoteOperationResult(mResultCode); + datas.add(syncedState.getSyncedFolderId()); + final RemoteOperationResult finalResult = new RemoteOperationResult(resultCode); finalResult.setData(datas); return finalResult; } @@ -192,14 +164,14 @@ public class UploadFileOperation extends RemoteOperation implements Parcelable { * 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() ; + private UploadFileRemoteOperation buildUploadOperation(File file, String targetPath) { + String timeStamp = ((Long) (file.lastModified() / 1000) ).toString() ; //create UploadFileOperation - UploadFileRemoteOperation uploadRemoteFileOperation = new UploadFileRemoteOperation( mSyncedState.getLocalPath(), - ( targetPath != null ) ? targetPath : mSyncedState.getRemotePath(), - CommonUtils.getMimeType( file ), - ( !this.mSyncedState.isMediaType() || mSyncedState.getLastETAG().isEmpty() )? null : mSyncedState.getLastETAG(), //If not null, This can cause error 412; that means remote file has change + 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; @@ -211,35 +183,22 @@ public class UploadFileOperation extends RemoteOperation implements Parcelable { * @return RemoteOperationResult */ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - public RemoteOperationResult checkAvailableSpace(OwnCloudClient client, long fileSize){ + public RemoteOperationResult checkAvailableSpace(OwnCloudClient client, long fileSize) { GetRemoteUserInfoOperation getRemoteUserInfoOperation = new GetRemoteUserInfoOperation(); RemoteOperationResult ocsResult = getRemoteUserInfoOperation.execute(client); - if(ocsResult.isSuccess() && ocsResult.getData() != null){ + 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 ) { + if (((UserInfo) ocsResult.getData().get(0)).getQuota().getFree() < fileSize ) { Log.w(TAG, "quota exceeded!"); return new RemoteOperationResult(ResultCode.QUOTA_EXCEEDED); - }else{ + } else { Log.d(TAG, "Quota Okay"); return new RemoteOperationResult(ResultCode.OK); } - }else{ + } else { Log.w(TAG, "getRemoteUserInfoOperation failed: "+ocsResult.getHttpCode() ); return new RemoteOperationResult(ocsResult.getCode()); } } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(restartCounter); - dest.writeLong(previousLastModified); - dest.writeParcelable(mSyncedState, flags); - dest.writeLong(availableQuota); - } } 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 e03d4a55..cd4de9fe 100644 --- a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java +++ b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java @@ -222,7 +222,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation switch (request.getOperationType()){ case UPLOAD: final SyncedFileState sfs = request.getSyncedFileState(); - operation = new UploadFileOperation(sfs); + operation = new UploadFileOperation(sfs, getApplicationContext()); break; case DOWNLOAD: final DownloadRequest downloadRequest = (DownloadRequest) request; -- GitLab From 18f443138b4e25f9c83e183a096037099110daea Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Wed, 23 Mar 2022 11:07:18 +0100 Subject: [PATCH 07/25] remove deprecated code in CommonUtils. haveNetworkConnexion(Context context) --- .../foundation/e/drive/utils/CommonUtils.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 21c2099b..92abc6aa 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -17,6 +17,7 @@ import android.content.ContentResolver; import android.content.Context; import android.media.MediaScannerConnection; import android.net.ConnectivityManager; +import android.net.Network; import android.net.NetworkInfo; import android.net.Uri; import android.util.Log; @@ -195,14 +196,13 @@ public abstract class CommonUtils { boolean haveConnectedMobile = false; ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo[] netInfo = cm.getAllNetworkInfo(); - for (NetworkInfo ni : netInfo) { - if (ni.getType()== ConnectivityManager.TYPE_WIFI) // Replaced the ni.getTypeName by ni.getType to make the test from ObserverServiceTest to work. But looks a better solution in all case - if (ni.isConnected()) - haveConnectedWifi = true; - if (ni.getType()== ConnectivityManager.TYPE_MOBILE) - if (ni.isConnected()) - haveConnectedMobile = true; + Network[] networks = cm.getAllNetworks(); + for (Network network : networks) { + NetworkInfo networkInfo = cm.getNetworkInfo(network); + if (networkInfo.getType()== ConnectivityManager.TYPE_WIFI) + haveConnectedWifi |= networkInfo.isConnected(); + if (networkInfo.getType()== ConnectivityManager.TYPE_MOBILE) + haveConnectedMobile |= networkInfo.isConnected(); } return haveConnectedWifi || haveConnectedMobile; } -- GitLab From b798c44df9f906378d0adad2e2b17741f075e0af Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Wed, 23 Mar 2022 11:08:37 +0100 Subject: [PATCH 08/25] FullScanWorker is only trigger with unmetered network --- app/src/main/java/foundation/e/drive/utils/CommonUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 92abc6aa..9179a152 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -289,7 +289,7 @@ public abstract class CommonUtils { */ public static void registerPeriodicFullScanWorker(WorkManager workManager){ final Constraints constraints = new Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) + .setRequiredNetworkType(NetworkType.UNMETERED) .setRequiresBatteryNotLow(true) .build(); -- GitLab From 3a407d47a4f954f85bc86704ae65b4aa4819e515 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Wed, 23 Mar 2022 14:25:22 +0100 Subject: [PATCH 09/25] rewrite commonUtils.haveNetworkConnexion() --- .../foundation/e/drive/utils/CommonUtils.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) 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 9179a152..74d80836 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -18,6 +18,7 @@ import android.content.Context; import android.media.MediaScannerConnection; import android.net.ConnectivityManager; import android.net.Network; +import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.Uri; import android.util.Log; @@ -192,21 +193,21 @@ public abstract class CommonUtils { */ public static boolean haveNetworkConnexion(Context context) { Log.i(TAG, "haveNetworkConnexion()"); - boolean haveConnectedWifi = false; - boolean haveConnectedMobile = false; - - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - Network[] networks = cm.getAllNetworks(); - for (Network network : networks) { - NetworkInfo networkInfo = cm.getNetworkInfo(network); - if (networkInfo.getType()== ConnectivityManager.TYPE_WIFI) - haveConnectedWifi |= networkInfo.isConnected(); - if (networkInfo.getType()== ConnectivityManager.TYPE_MOBILE) - haveConnectedMobile |= networkInfo.isConnected(); + + ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); + NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork()); + + if (capabilities != null + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) { + return true; } - return haveConnectedWifi || haveConnectedMobile; + return false; } + + /** * Get mimetype of file from the file itself * -- GitLab From d4d76685bd9363e482f05e6107322759906cdee5 Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Thu, 24 Mar 2022 08:04:54 +0000 Subject: [PATCH 10/25] E1 clean download file operation --- .../operations/DownloadFileOperation.java | 146 +++++++----------- .../services/SynchronizationService.java | 2 +- 2 files changed, 54 insertions(+), 94 deletions(-) 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 c8d17bb8..4445239c 100644 --- a/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java @@ -9,8 +9,6 @@ package foundation.e.drive.operations; import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; import android.util.Log; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.operations.RemoteOperation; @@ -26,153 +24,115 @@ import foundation.e.drive.utils.CommonUtils; * @author Vincent Bourgmayer * Encapsulate a global download process for a file */ -public class DownloadFileOperation extends RemoteOperation implements Parcelable { +public class DownloadFileOperation extends RemoteOperation { private final static String TAG = DownloadFileOperation.class.getSimpleName(); - private final RemoteFile mRFile; - private Context mContext; - private String mTargetPath; + private final RemoteFile remoteFile; + private Context context; + private String targetPath; private int restartCounter =0; - private SyncedFileState mSyncedState; + private SyncedFileState syncedFileState; private String previousEtag; - /** * COnstructor of download operation where syncedFileState is already known * @param remoteFile remote file to Download * @param syncedFileState SyncedFileState corresponding to remote file */ - public DownloadFileOperation(RemoteFile remoteFile, SyncedFileState syncedFileState){ this.mRFile = remoteFile; - this.mSyncedState = syncedFileState; - this.previousEtag = mSyncedState.getLastETAG(); - this.mTargetPath = this.mSyncedState.getLocalPath(); - } - - protected DownloadFileOperation(Parcel in) { - mRFile = in.readParcelable(RemoteFile.class.getClassLoader()); - mTargetPath = in.readString(); - restartCounter = in.readInt(); - mSyncedState = in.readParcelable(SyncedFileState.class.getClassLoader()); - previousEtag = in.readString(); + public DownloadFileOperation(RemoteFile remoteFile, SyncedFileState syncedFileState, Context context) { + this.remoteFile = remoteFile; + this.syncedFileState = syncedFileState; + this.previousEtag = syncedFileState.getLastETAG(); + this.targetPath = syncedFileState.getLocalPath(); + this.context = context; } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(mRFile, flags); - dest.writeString(mTargetPath); - dest.writeInt(restartCounter); - dest.writeParcelable(mSyncedState, flags); - dest.writeString(previousEtag); - } - - - public static final Creator CREATOR = new Creator() { - @Override - public DownloadFileOperation createFromParcel(Parcel in) { - return new DownloadFileOperation(in); - } - - @Override - public DownloadFileOperation[] newArray(int size) { - return new DownloadFileOperation[size]; - } - }; - - public void setContext(Context context){ - this.mContext = context; - } - - @Override protected RemoteOperationResult run(OwnCloudClient ownCloudClient) { Log.i(TAG, "run(ownCloudClient)"); //get or build synced file equivalent of this.mFile - if(mSyncedState == null || mTargetPath == null || mTargetPath.isEmpty()) { - Log.e(TAG, "mSyncedState or mTargetPath is empty or null. Can't Download in those conditions"); + 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(mSyncedState.getId() == -1){ - this.mSyncedState.setId( DbHelper.manageSyncedFileStateDB(this.mSyncedState, "INSERT", mContext) ); + } else if (syncedFileState.getId() == -1) { + this.syncedFileState.setId(DbHelper.manageSyncedFileStateDB(this.syncedFileState, "INSERT", context)); } - if(mSyncedState.getLastETAG().equals( mRFile.getEtag() ) && mSyncedState.getLocalLastModified() > 0L){ + if (syncedFileState.getLastETAG().equals(remoteFile.getEtag()) && syncedFileState.getLocalLastModified() > 0L) { //Same etag and localLastModified not null mean the file is up to date Log.w(TAG, "File already up-to-date"); - return new RemoteOperationResult(RemoteOperationResult.ResultCode.ETAG_UNCHANGED ); + return new RemoteOperationResult(RemoteOperationResult.ResultCode.ETAG_UNCHANGED); } - String tmpTargetPath = mContext.getExternalCacheDir()+ FileUtils.PATH_SEPARATOR+mSyncedState.getName(); - DownloadFileRemoteOperation downloadOperation = new DownloadFileRemoteOperation(mRFile.getRemotePath(), + + final String tmpTargetPath = context.getExternalCacheDir()+ FileUtils.PATH_SEPARATOR+ syncedFileState.getName(); + final DownloadFileRemoteOperation downloadOperation = new DownloadFileRemoteOperation(remoteFile.getRemotePath(), tmpTargetPath); - RemoteOperationResult downloadResult = downloadOperation.execute( ownCloudClient ); - RemoteOperationResult.ResultCode mResultCode; + final RemoteOperationResult downloadResult = downloadOperation.execute(ownCloudClient); + RemoteOperationResult.ResultCode resultCode; boolean mustRestart = true; - if( downloadResult.isSuccess() ){ - File tmpLocalFile = new File(tmpTargetPath); - if( !tmpLocalFile.exists() ){ + if (downloadResult.isSuccess()) { + final File tmpLocalFile = new File(tmpTargetPath); + if (!tmpLocalFile.exists()) { Log.e(TAG, "Downloaded file doesn't exist or is null"); - mResultCode = RemoteOperationResult.ResultCode.FILE_NOT_FOUND; + resultCode = RemoteOperationResult.ResultCode.FILE_NOT_FOUND; - }else if(tmpLocalFile.length() != mRFile.getLength() ){ + } else if (tmpLocalFile.length() != remoteFile.getLength()) { Log.e(TAG, "Local and remote file doesn't have the same size."); - mResultCode = RemoteOperationResult.ResultCode.INVALID_OVERWRITE; + resultCode = RemoteOperationResult.ResultCode.INVALID_OVERWRITE; tmpLocalFile.delete(); - }else{ + } else { //file has been correctly download. - File localFile = new File(mTargetPath); - if( localFile.exists() ){ + final File localFile = new File(targetPath); + if (localFile.exists()) { localFile.delete(); } //Check parentFolder existence and create if needed - String parentFoldersPath = localFile.getParent(); - File localParentFile = new File(parentFoldersPath); - if( !localParentFile.exists() ){ - if( localParentFile.mkdirs() ) - Log.d(TAG, "Created folders: "+parentFoldersPath ); + 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 ); + Log.d(TAG, "Can't create folders: "+parentFoldersPath); } boolean renameResult = tmpLocalFile.renameTo(localFile); - if(!renameResult) + if (!renameResult) Log.d(TAG, "File hasn't been successfully moved at its place"); - mSyncedState.setLocalLastModified( localFile.lastModified() ) - .setLastETAG( mRFile.getEtag() ); + syncedFileState.setLocalLastModified(localFile.lastModified()) + .setLastETAG(remoteFile.getEtag()); mustRestart = false; - mResultCode = RemoteOperationResult.ResultCode.OK; + resultCode = RemoteOperationResult.ResultCode.OK; //needed to make Gallery show new image - CommonUtils.doActionMediaScannerConnexionScanFile(mContext, mSyncedState.getLocalPath() ); + CommonUtils.doActionMediaScannerConnexionScanFile(context, syncedFileState.getLocalPath()); } - }else{ + } else { //If download failed - Log.e(TAG, "Download failed: "+downloadResult.getLogMessage() ); - mResultCode = RemoteOperationResult.ResultCode.UNKNOWN_ERROR; + Log.e(TAG, "Download failed: "+downloadResult.getLogMessage()); + resultCode = RemoteOperationResult.ResultCode.UNKNOWN_ERROR; } - if(mustRestart){ + if (mustRestart) { Log.w(TAG, restartCounter+" unsuccessfull trial.s of downloading file " - +mRFile.getRemotePath() ); - mSyncedState.setLastETAG(this.previousEtag); - if(this.restartCounter < 3){ + + remoteFile.getRemotePath()); + syncedFileState.setLastETAG(this.previousEtag); + if (this.restartCounter < 3) { this.restartCounter += 1; return this.run(ownCloudClient); - }else{ - mResultCode = RemoteOperationResult.ResultCode.INVALID_OVERWRITE; + } else { + resultCode = RemoteOperationResult.ResultCode.INVALID_OVERWRITE; } } //So now, we can update instance of SyncedState and save it to DB - if( DbHelper.manageSyncedFileStateDB( mSyncedState, "UPDATE", mContext ) <= 0 ){ + if (DbHelper.manageSyncedFileStateDB(syncedFileState, "UPDATE", context) <= 0) { Log.e(TAG, "DB update failed: 0 affected row"); //@TODO : do smtg } - return new RemoteOperationResult( mResultCode ); - } - - @Override - public int describeContents() { - return 0; + return new RemoteOperationResult(resultCode); } } 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 cd4de9fe..a392af37 100644 --- a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java +++ b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java @@ -226,7 +226,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation break; case DOWNLOAD: final DownloadRequest downloadRequest = (DownloadRequest) request; - operation = new DownloadFileOperation(downloadRequest.getRemoteFile(), downloadRequest.getSyncedFileState()); + operation = new DownloadFileOperation(downloadRequest.getRemoteFile(), downloadRequest.getSyncedFileState(), getApplicationContext()); break; case REMOTE_DELETE: default: -- GitLab From 299defb6c6fd5a80f592cd1eb71c7d3f3aa44169 Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Thu, 24 Mar 2022 08:05:17 +0000 Subject: [PATCH 11/25] clean RemoveFileOperation --- .../drive/operations/RemoveFileOperation.java | 42 +++---------------- 1 file changed, 5 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/operations/RemoveFileOperation.java b/app/src/main/java/foundation/e/drive/operations/RemoveFileOperation.java index e70841c9..d1f4570b 100644 --- a/app/src/main/java/foundation/e/drive/operations/RemoveFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/RemoveFileOperation.java @@ -8,10 +8,6 @@ package foundation.e.drive.operations; -import android.os.Parcel; -import android.os.Parcelable; - -import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.resources.files.RemoveFileRemoteOperation; import foundation.e.drive.models.SyncedFileState; @@ -21,44 +17,16 @@ import foundation.e.drive.models.SyncedFileState; * Created by Vincent on 19/06/2018. * Class to be able to wrap concerned SyncedFileState in operation, so it can be retrieve at the end */ -public class RemoveFileOperation extends RemoveFileRemoteOperation implements Parcelable { +public class RemoveFileOperation extends RemoveFileRemoteOperation { - private SyncedFileState mSyncedFileState; + private SyncedFileState syncedFileState; public RemoveFileOperation(SyncedFileState syncedFileState) { - super( syncedFileState.getRemotePath() ); - this.mSyncedFileState = syncedFileState; - } - - protected RemoveFileOperation(Parcel in) { - super(in.readString()); - mSyncedFileState = in.readParcelable(SyncedFileState.class.getClassLoader()); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mSyncedFileState.getRemotePath()); - dest.writeParcelable(mSyncedFileState, flags); + super(syncedFileState.getRemotePath()); + this.syncedFileState = syncedFileState; } - @Override - public int describeContents() { - return 0; - } - - public static final Creator CREATOR = new Creator() { - @Override - public RemoveFileOperation createFromParcel(Parcel in) { - return new RemoveFileOperation(in); - } - - @Override - public RemoveFileOperation[] newArray(int size) { - return new RemoveFileOperation[size]; - } - }; - public SyncedFileState getSyncedFileState() { - return mSyncedFileState; + return syncedFileState; } } -- GitLab From d7f6d3be3b2c26cd5ca9a4d540a5eef79b1096a7 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Thu, 24 Mar 2022 09:13:58 +0100 Subject: [PATCH 12/25] fix typo in method name --- .../java/foundation/e/drive/services/ObserverService.java | 5 +---- .../foundation/e/drive/services/SynchronizationService.java | 2 +- app/src/main/java/foundation/e/drive/utils/CommonUtils.java | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) 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 a84cb696..943a15d7 100644 --- a/app/src/main/java/foundation/e/drive/services/ObserverService.java +++ b/app/src/main/java/foundation/e/drive/services/ObserverService.java @@ -11,10 +11,8 @@ package foundation.e.drive.services; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Service; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.os.Handler; @@ -44,7 +42,6 @@ import foundation.e.drive.models.SyncRequest; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.operations.ListFileRemoteOperation; -import foundation.e.drive.operations.RemoveFileOperation; import foundation.e.drive.receivers.ForceSyncReceiver; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; @@ -145,7 +142,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene } //check for the case where intent has been launched by initializerService - if (!CommonUtils.haveNetworkConnexion(this)) { + if (!CommonUtils.haveNetworkConnection(this)) { Log.w(TAG, "There is no Internet connexion."); return super.onStartCommand( intent, flags, startId ); } 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 cd4de9fe..1a11ca76 100644 --- a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java +++ b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java @@ -118,7 +118,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation private void startWorker(int threadIndex){ if (syncedRequestQueue.isEmpty()) return; - if (!threadWorkingState[threadIndex] && CommonUtils.haveNetworkConnexion(getApplicationContext())) { //check if the thread corresponding to threadIndex isn't already working + if (!threadWorkingState[threadIndex] && CommonUtils.haveNetworkConnection(getApplicationContext())) { //check if the thread corresponding to threadIndex isn't already working final SyncRequest request = this.syncedRequestQueue.poll(); //return null if deque is empty 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 74d80836..a7fcdf0c 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -191,8 +191,8 @@ public abstract class CommonUtils { * @param context Activity or service which are calling this method * @return True if there is connexion, false either */ - public static boolean haveNetworkConnexion(Context context) { - Log.i(TAG, "haveNetworkConnexion()"); + public static boolean haveNetworkConnection(Context context) { + Log.i(TAG, "haveNetworkConnection()"); ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork()); -- GitLab From 942996f1f3dc136d1b1d0c304c7f4a0eed3093db Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Thu, 24 Mar 2022 11:32:51 +0000 Subject: [PATCH 13/25] E1 clean initializer service --- .../e/drive/services/InitializerService.java | 149 ++++++++---------- 1 file changed, 66 insertions(+), 83 deletions(-) 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 6b208015..ef08ccb2 100644 --- a/app/src/main/java/foundation/e/drive/services/InitializerService.java +++ b/app/src/main/java/foundation/e/drive/services/InitializerService.java @@ -25,7 +25,6 @@ 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.Arrays; import java.util.List; @@ -36,7 +35,6 @@ import foundation.e.drive.operations.CreateInitialFolderRemoteOperation; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; - import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; import static foundation.e.drive.utils.AppConstants.INITIALFOLDERS_NUMBER; import static foundation.e.drive.utils.AppConstants.MEDIA_SYNCABLE_CATEGORIES; @@ -52,10 +50,10 @@ public class InitializerService extends Service implements OnRemoteOperationList private final String TAG = InitializerService.class.getSimpleName(); private int existingRemoteFolderCounter; //Temporarily used to know if all remotePath exist - private List mSyncedFolders; - private OwnCloudClient mCloudClient; - private Handler mHandler; - private Account mAccount; + private List syncedFolders; + private OwnCloudClient cloudClient; + private Handler handler; + private Account account; private int restartFolderCreationCounter =0; @Override @@ -66,39 +64,37 @@ public class InitializerService extends Service implements OnRemoteOperationList } @Override - public int onStartCommand( Intent intent, int flags, int startId ) { + public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand(...)"); CommonUtils.setServiceUnCaughtExceptionHandler(this); //Get account - SharedPreferences prefs = this.getSharedPreferences( AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE ); - - - if (prefs.getBoolean( AppConstants.INITIALIZATION_HAS_BEEN_DONE, false ) ) { + SharedPreferences prefs = this.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + if (prefs.getBoolean(AppConstants.INITIALIZATION_HAS_BEEN_DONE, false)) { Log.w(TAG, "Initializer has already been run"); } else { - String accountName = prefs.getString( AccountManager.KEY_ACCOUNT_NAME, "" ); - String accountType = prefs.getString( AccountManager.KEY_ACCOUNT_TYPE, "" ); + String accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, ""); + String accountType = prefs.getString(AccountManager.KEY_ACCOUNT_TYPE, ""); - if ( accountName.isEmpty() && accountType.isEmpty() && intent.getExtras() != null ) { + if (accountName.isEmpty() && accountType.isEmpty() && intent.getExtras() != null) { - accountName = intent.getExtras().getString( AccountManager.KEY_ACCOUNT_NAME, "" ); - accountType = intent.getExtras().getString( AccountManager.KEY_ACCOUNT_TYPE, "" ); + accountName = intent.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME, ""); + accountType = intent.getExtras().getString(AccountManager.KEY_ACCOUNT_TYPE, ""); - prefs.edit().putString( AccountManager.KEY_ACCOUNT_NAME, accountName ) - .putString( AccountManager.KEY_ACCOUNT_TYPE, accountType ) + prefs.edit().putString(AccountManager.KEY_ACCOUNT_NAME, accountName) + .putString(AccountManager.KEY_ACCOUNT_TYPE, accountType) .apply(); } - if (accountName.isEmpty() ) { + if (accountName.isEmpty()) { Log.w(TAG, "Account's name not found. Neither in shared prefs nor in intent's extras"); stopSelf(); } else { - this.mAccount = CommonUtils.getAccount( accountName, accountType, AccountManager.get(this) ); + this.account = CommonUtils.getAccount(accountName, accountType, AccountManager.get(this)); //Get OwnCloudlient - if (this.mAccount != null) { - this.mCloudClient = CommonUtils.getOwnCloudClient( this.mAccount, getApplicationContext()); + if (this.account != null) { + this.cloudClient = CommonUtils.getOwnCloudClient(this.account, getApplicationContext()); start(); } else { Log.w(TAG, "Got account is invalid."); @@ -109,25 +105,20 @@ public class InitializerService extends Service implements OnRemoteOperationList return super.onStartCommand(intent, flags, startId); } - - /** - * start to do its job - */ - public void start() { Log.i(TAG, "start()"); - if (mCloudClient == null){ + if (cloudClient == null) { stopSelf(); return; } List syncCategories = new ArrayList<>(); - if (CommonUtils.isMediaSyncEnabled(mAccount)) { + if (CommonUtils.isMediaSyncEnabled(account)) { syncCategories.addAll(Arrays.asList(MEDIA_SYNCABLE_CATEGORIES)); } - if (CommonUtils.isSettingsSyncEnabled(mAccount)) { + if (CommonUtils.isSettingsSyncEnabled(account)) { syncCategories.addAll(Arrays.asList(SETTINGS_SYNCABLE_CATEGORIES)); } @@ -136,101 +127,95 @@ public class InitializerService extends Service implements OnRemoteOperationList this.existingRemoteFolderCounter = 0; CreateNextRemoteFolder(); - } + } /** * Return a list of SyncedFolder * @param categories categories indicating which syncedFolder to create */ - private void getInitialSyncedFolders( List categories){ + private void getInitialSyncedFolders(List categories) { Log.i(TAG, "getInitialSyncedFolders"); - this.mSyncedFolders = new ArrayList<>(); + this.syncedFolders = new ArrayList<>(); - for(int i=-1, size = categories.size(); ++i < size;){ + for(int i=-1, size = categories.size(); ++i < size;) { final String DEVICE_SPECIFIC_PATH = PATH_SEPARATOR+"Devices"+PATH_SEPARATOR+ Build.BRAND+"_"+ Build.MODEL+"_" + Build.SERIAL; - switch ( categories.get(i) ){ + switch (categories.get(i)) { case "Medias": break; case "Images": - mSyncedFolders.add( new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_DCIM), - "/Photos/", true) ); - mSyncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_PICTURES), - "/Pictures/", true ) ); + syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_DCIM), + "/Photos/", true)); + syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_PICTURES), + "/Pictures/", true)); break; case "Movies": - mSyncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_MOVIES), + syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_MOVIES), "/Movies/", true)); break; case "Music": - mSyncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_MUSIC), + syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_MUSIC), "/Music/", true)); break; case "Ringtones": - mSyncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_RINGTONES), + syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_RINGTONES), "/Ringtones/", true)); break; case "Documents": - mSyncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_DOCUMENTS), + syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_DOCUMENTS), "/Documents/", true)); break; case "Podcasts": - mSyncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_PODCASTS), + syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_PODCASTS), "/Podcasts/", true)); break; case "Rom settings": String remoteFolderPath = DEVICE_SPECIFIC_PATH+"/rom_settings/"; - mSyncedFolders.add( new SyncedFolder(categories.get(i), "/data/system/users/0/", remoteFolderPath, true, false, true, false) ); + syncedFolders.add(new SyncedFolder(categories.get(i), "/data/system/users/0/", remoteFolderPath, true, false, true, false)); try{ - mSyncedFolders.add( new SyncedFolder( + syncedFolders.add(new SyncedFolder( categories.get(i), getFilesDir().getCanonicalPath()+PATH_SEPARATOR, remoteFolderPath+"app_list/", true, false, true, - false) ); - } catch (Exception e){ Log.e(TAG, e.toString()); } - break; + false)); + } catch (Exception e) { Log.e(TAG, e.toString()); } + break; } } } - private String getExternalFolder(String directory){ + private String getExternalFolder(String directory) { return CommonUtils.getLocalPath(Environment.getExternalStoragePublicDirectory(directory))+ PATH_SEPARATOR; } - /** - * Start to createSyncedFolder in the cloud - */ - private void CreateNextRemoteFolder(){ + private void CreateNextRemoteFolder() { Log.i(TAG, "createNextRemoteFolder()"); this.restartFolderCreationCounter = 0; - if (this.mSyncedFolders == null || this.mSyncedFolders.isEmpty() ){ + if (this.syncedFolders == null || this.syncedFolders.isEmpty()) { this.stopSelf(); } //It means that there are still folders to create - if (this.existingRemoteFolderCounter < this.mSyncedFolders.size() ){ - - if (this.mHandler == null ) this.mHandler = new Handler(); + if (this.existingRemoteFolderCounter < this.syncedFolders.size()) { + if (this.handler == null) this.handler = new Handler(); CreateInitialFolderRemoteOperation createFolderOperation = new CreateInitialFolderRemoteOperation( - this.mSyncedFolders.get( this.existingRemoteFolderCounter ), + this.syncedFolders.get(this.existingRemoteFolderCounter), true, this); - createFolderOperation.execute(this.mCloudClient, this, this.mHandler); - - } else if (this.existingRemoteFolderCounter == this.mSyncedFolders.size() ){ + createFolderOperation.execute(this.cloudClient, this, this.handler); + } else if (this.existingRemoteFolderCounter == this.syncedFolders.size()) { doLastStep(); - } else { - Log.e(TAG, "this.existingRemoteFolderCounter : "+this.existingRemoteFolderCounter+" > this.mSyncedFolders.size() : "+this.mSyncedFolders.size() ); + Log.e(TAG, "this.existingRemoteFolderCounter : "+this.existingRemoteFolderCounter+" > this.mSyncedFolders.size() : "+this.syncedFolders.size()); this.stopSelf(); } } @@ -238,27 +223,25 @@ public class InitializerService extends Service implements OnRemoteOperationList @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { Log.i(TAG, "onRemoteOperationFinish()"); - if (operation instanceof CreateInitialFolderRemoteOperation){ + if (operation instanceof CreateInitialFolderRemoteOperation) { - if (result.isSuccess() || result.getCode() == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS){ + if (result.isSuccess() || result.getCode() == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS) { this.existingRemoteFolderCounter+=1; CreateNextRemoteFolder(); - } else if (result.getHttpCode() == 423 || result.getHttpCode() == 409){//file locked or conflict in result - Log.e( TAG, result.getLogMessage() ); + } else if (result.getHttpCode() == 423 || result.getHttpCode() == 409) {//file locked or conflict in result + Log.e(TAG, result.getLogMessage()); if (this.restartFolderCreationCounter < 3) { - Log.w( TAG, " restart operation" ); - operation.execute( this.mCloudClient, this, this.mHandler ); + Log.w(TAG, " restart operation"); + operation.execute(this.cloudClient, this, this.handler); this.restartFolderCreationCounter+=1; - } else { Log.e(TAG, "Remote folder's creation failed due to conflict with server"); stopSelf(); } - } else { - Log.e(TAG, result.getLogMessage()+" "+result.getHttpCode() ); + Log.e(TAG, result.getLogMessage()+" "+result.getHttpCode()); stopSelf(); } } @@ -268,20 +251,20 @@ public class InitializerService extends Service implements OnRemoteOperationList /** * Function to check if all remote folder have been created **/ - private void doLastStep(){ + private void doLastStep() { Log.i(TAG, "doLastStep()"); final Context appContext = getApplicationContext(); - appContext.getSharedPreferences( AppConstants.SHARED_PREFERENCE_NAME, - Context.MODE_PRIVATE ) + appContext.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, + Context.MODE_PRIVATE) .edit() .putBoolean(AppConstants.INITIALIZATION_HAS_BEEN_DONE, true) - .putInt( INITIALFOLDERS_NUMBER, mSyncedFolders.size() ) + .putInt(INITIALFOLDERS_NUMBER, syncedFolders.size()) .apply(); CommonUtils.registerPeriodicFullScanWorker(WorkManager.getInstance(appContext)); //all folder have been created - ((EdriveApplication) this.getApplication() ).startRecursiveFileObserver(); + ((EdriveApplication) this.getApplication()).startRecursiveFileObserver(); Intent SynchronizationServiceIntent = new Intent(getApplicationContext(), foundation.e.drive.services.SynchronizationService.class); startService(SynchronizationServiceIntent); @@ -296,11 +279,11 @@ public class InitializerService extends Service implements OnRemoteOperationList @Override public void onDestroy() { super.onDestroy(); - this.mHandler = null; - this.mAccount = null; - this.mCloudClient = null; - if(this.mSyncedFolders != null) this.mSyncedFolders.clear(); - this.mSyncedFolders = null; + this.handler = null; + this.account = null; + this.cloudClient = null; + if (this.syncedFolders != null) this.syncedFolders.clear(); + this.syncedFolders = null; } @Nullable -- GitLab From f6cc492b520ff18a66179ee6a12d2d5ab5207f45 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Thu, 24 Mar 2022 14:49:29 +0100 Subject: [PATCH 14/25] remove any references and usage of OMS_IS_WORKING preferences --- .../main/java/foundation/e/drive/EdriveApplication.java | 8 -------- .../java/foundation/e/drive/services/ObserverService.java | 7 ------- .../java/foundation/e/drive/services/ResetService.java | 1 - .../e/drive/services/SynchronizationService.java | 5 +---- .../main/java/foundation/e/drive/utils/AppConstants.java | 6 ------ .../foundation/e/drive/services/AbstractServiceIT.java | 1 - 6 files changed, 1 insertion(+), 27 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/EdriveApplication.java b/app/src/main/java/foundation/e/drive/EdriveApplication.java index e36b5752..fcb1a0f8 100644 --- a/app/src/main/java/foundation/e/drive/EdriveApplication.java +++ b/app/src/main/java/foundation/e/drive/EdriveApplication.java @@ -38,7 +38,6 @@ public class EdriveApplication extends Application { super.onCreate(); fileEventListener = new FileEventListener(getApplicationContext()); Log.i(TAG, "Starting"); - resetOperationManagerSetting(); final String pathForObserver = Environment.getExternalStorageDirectory().getAbsolutePath(); mFileObserver = new RecursiveFileObserver(getApplicationContext(), pathForObserver, fileEventListener); @@ -65,13 +64,6 @@ public class EdriveApplication extends Application { } } - private void resetOperationManagerSetting() { - getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE).edit() - .putBoolean(AppConstants.KEY_OMS_IS_WORKING, false) - .apply(); - } - - /** * Start Recursive FileObserver if not already watching */ 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 943a15d7..cdcbe8d5 100644 --- a/app/src/main/java/foundation/e/drive/services/ObserverService.java +++ b/app/src/main/java/foundation/e/drive/services/ObserverService.java @@ -121,12 +121,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene return super.onStartCommand(intent,flags,startId); } - //check OperationManagerService isn't working - if (prefs.getBoolean(AppConstants.KEY_OMS_IS_WORKING, false)){ - Log.w(TAG, "OperationManagerService is still performing some operation"); - return super.onStartCommand(intent,flags, startId); - } - //Check a minimum delay has been respected between two start. long lastSyncTime = prefs.getLong(AppConstants.KEY_LAST_SYNC_TIME, 0L); long currentTime = System.currentTimeMillis(); @@ -310,7 +304,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) .edit() .putLong(AppConstants.KEY_LAST_SYNC_TIME, System.currentTimeMillis()) - .putBoolean(AppConstants.KEY_OMS_IS_WORKING, false) .apply(); } this.isWorking = false; diff --git a/app/src/main/java/foundation/e/drive/services/ResetService.java b/app/src/main/java/foundation/e/drive/services/ResetService.java index 395e2acb..a9eb6e3f 100644 --- a/app/src/main/java/foundation/e/drive/services/ResetService.java +++ b/app/src/main/java/foundation/e/drive/services/ResetService.java @@ -89,7 +89,6 @@ public class ResetService extends Service { .remove(AccountManager.KEY_ACCOUNT_TYPE) .putBoolean(INITIALIZATION_HAS_BEEN_DONE, false) .remove(INITIALFOLDERS_NUMBER) - .remove(AppConstants.KEY_OMS_IS_WORKING) .remove(AppConstants.KEY_LAST_SYNC_TIME) .apply(); } 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 43fd4861..aad49270 100644 --- a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java +++ b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java @@ -95,10 +95,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation @Override public void onLowMemory() { - Log.w(TAG, "System is low on memory. Service might get killed. Setting KEY_OMS_IS_WORKING to false"); - getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE).edit() - .putBoolean(AppConstants.KEY_OMS_IS_WORKING, false) - .apply(); + Log.w(TAG, "System is low on memory. Service might get killed."); } public boolean queueOperation(SyncRequest request){ 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 7506a525..4c3be31d 100644 --- a/app/src/main/java/foundation/e/drive/utils/AppConstants.java +++ b/app/src/main/java/foundation/e/drive/utils/AppConstants.java @@ -8,7 +8,6 @@ package foundation.e.drive.utils; - import android.os.Build; import foundation.e.drive.BuildConfig; @@ -17,11 +16,9 @@ import java.util.Locale; import foundation.e.drive.BuildConfig; - /** * @author Vincent Bourgmayer */ -//Contains some constant value public abstract class AppConstants { public static final String MEDIASYNC_PROVIDER_AUTHORITY ="foundation.e.drive.providers.MediasSyncProvider"; @@ -33,7 +30,6 @@ public abstract class AppConstants { public static final String APPLICATIONS_LIST_FILE_NAME_TMP = "tmp_packages_list.csv"; public static final String SHARED_PREFERENCE_NAME ="preferences"; public static final String KEY_LAST_SYNC_TIME = "lastSyncTimestamp"; - public static final String KEY_OMS_IS_WORKING = "OMS_is_working"; 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"}; @@ -53,6 +49,4 @@ public abstract class AppConstants { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd", Locale.getDefault()); return sdf.format(ts); } - - } 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 810a7640..27519cb0 100644 --- a/app/src/test/java/foundation/e/drive/services/AbstractServiceIT.java +++ b/app/src/test/java/foundation/e/drive/services/AbstractServiceIT.java @@ -111,7 +111,6 @@ public abstract class AbstractServiceIT { */ protected void registerSharedPref(){ sharedPreferences.edit().putBoolean( AppConstants.INITIALIZATION_HAS_BEEN_DONE, init_done) - .putBoolean(AppConstants.KEY_OMS_IS_WORKING, oms_running) .putString(AccountManager.KEY_ACCOUNT_NAME, TEST_ACCOUNT_NAME) .putString(AccountManager.KEY_ACCOUNT_TYPE, TEST_ACCOUNT_TYPE) .putInt(AppConstants.INITIALFOLDERS_NUMBER, initial_folder_number) -- GitLab From 42c9ca4041857ddb341ac624ba6bd6fc631f46b2 Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Fri, 25 Mar 2022 06:38:59 +0000 Subject: [PATCH 15/25] Fix remote folder creation when missing for file upload --- .../foundation/e/drive/operations/UploadFileOperation.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 e0fdfe24..ff3f00c8 100644 --- a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java @@ -110,9 +110,9 @@ public class UploadFileOperation extends RemoteOperation { mustRestart = false; } else { //Si les répértoires ou mettre le fichier n'existe pas, on les ajoutes. - if (uploadResult.getCode() == ResultCode.FILE_NOT_FOUND ) { - resultCode = ResultCode.FILE_NOT_FOUND; - Log.d(TAG, "Catched a File not found result for : "+file.getName()+", create missing remote path then retry"); + if (uploadResult.getCode() == ResultCode.CONFLICT ) { + resultCode = ResultCode.CONFLICT; + Log.d(TAG, "Catched a conflict result for : "+file.getName()+", create missing remote path then retry"); final String remoteFolderPath = targetPath.substring(0, targetPath.lastIndexOf(FileUtils.PATH_SEPARATOR)+1 ); final CreateFolderRemoteOperation createFolderOperation = new CreateFolderRemoteOperation(remoteFolderPath, true ); try{ -- GitLab From 9addc3317458d07316d21bab54aa02e465528ccc Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Thu, 31 Mar 2022 15:13:12 +0000 Subject: [PATCH 16/25] handle Initialization with Work Request --- .../CreateInitialFolderRemoteOperation.java | 64 --------- .../e/drive/services/InitializerService.java | 120 +---------------- .../e/drive/utils/AppConstants.java | 10 +- .../foundation/e/drive/utils/CommonUtils.java | 89 +++++++++++- .../drive/work/CreateRemoteFolderWorker.java | 127 ++++++++++++++++++ .../e/drive/work/FirstStartWorker.java | 60 +++++++++ 6 files changed, 286 insertions(+), 184 deletions(-) delete mode 100644 app/src/main/java/foundation/e/drive/operations/CreateInitialFolderRemoteOperation.java create mode 100644 app/src/main/java/foundation/e/drive/work/CreateRemoteFolderWorker.java create mode 100644 app/src/main/java/foundation/e/drive/work/FirstStartWorker.java diff --git a/app/src/main/java/foundation/e/drive/operations/CreateInitialFolderRemoteOperation.java b/app/src/main/java/foundation/e/drive/operations/CreateInitialFolderRemoteOperation.java deleted file mode 100644 index 72b837a3..00000000 --- a/app/src/main/java/foundation/e/drive/operations/CreateInitialFolderRemoteOperation.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright © Vincent Bourgmayer (/e/ foundation). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package foundation.e.drive.operations; - -import android.content.Context; -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.CreateFolderRemoteOperation; -import java.io.File; -import foundation.e.drive.database.DbHelper; -import foundation.e.drive.models.SyncedFolder; - -/** - * @author Vincent Bourgmayer - * Perform initial folder creation - */ -public class CreateInitialFolderRemoteOperation extends RemoteOperation { - private static final String TAG = CreateInitialFolderRemoteOperation.class.getSimpleName(); - - private SyncedFolder mSyncedFolder; - private String mRemotePath; - private boolean mCreateFullPath;//should recreate parent path if not exist or not - private final Context mContext; - - public CreateInitialFolderRemoteOperation(SyncedFolder root, boolean createFullPath, Context context) { - super(); - this.mSyncedFolder = root; - this.mRemotePath = root.getRemoteFolder(); - this.mCreateFullPath = createFullPath; - this.mContext = context; - } - - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - File folder = new File(mSyncedFolder.getLocalFolder() ); - if( !folder.exists() ){ - Log.e(TAG, "Local folder doesn't exist, so create it"); - folder.mkdirs(); - } - - //Perfom request - CreateFolderRemoteOperation createFolderOperation = new CreateFolderRemoteOperation(mRemotePath, mCreateFullPath); - RemoteOperationResult createOperationResult = createFolderOperation.execute(client, true); - - if(createOperationResult.isSuccess() || createOperationResult.getCode() == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS ){ - if(DbHelper.insertSyncedFolder(mSyncedFolder, mContext) >= 0 ) { - Log.d(TAG, "Insertion in DB succeed"); - return new RemoteOperationResult(RemoteOperationResult.ResultCode.OK); - }else { - Log.d(TAG, "insertion of folder in DB failed"); - return new RemoteOperationResult(RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS); - } - } - return createOperationResult; - } -} 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 ef08ccb2..d493028d 100644 --- a/app/src/main/java/foundation/e/drive/services/InitializerService.java +++ b/app/src/main/java/foundation/e/drive/services/InitializerService.java @@ -16,27 +16,20 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; import android.os.Environment; -import android.os.Handler; import android.os.IBinder; import android.util.Log; 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.Arrays; import java.util.List; -import foundation.e.drive.EdriveApplication; import foundation.e.drive.models.SyncedFolder; -import foundation.e.drive.operations.CreateInitialFolderRemoteOperation; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; -import static foundation.e.drive.utils.AppConstants.INITIALFOLDERS_NUMBER; import static foundation.e.drive.utils.AppConstants.MEDIA_SYNCABLE_CATEGORIES; import static foundation.e.drive.utils.AppConstants.SETTINGS_SYNCABLE_CATEGORIES; @@ -46,21 +39,16 @@ import androidx.work.WorkManager; /** * @author Vincent Bourgmayer */ -public class InitializerService extends Service implements OnRemoteOperationListener { +public class InitializerService extends Service { private final String TAG = InitializerService.class.getSimpleName(); - - private int existingRemoteFolderCounter; //Temporarily used to know if all remotePath exist private List syncedFolders; private OwnCloudClient cloudClient; - private Handler handler; private Account account; - private int restartFolderCreationCounter =0; @Override public void onCreate() { Log.i(TAG, "onCreate()"); super.onCreate(); - this.existingRemoteFolderCounter = 0; } @Override @@ -112,21 +100,13 @@ public class InitializerService extends Service implements OnRemoteOperationList return; } - List syncCategories = new ArrayList<>(); - - if (CommonUtils.isMediaSyncEnabled(account)) { - syncCategories.addAll(Arrays.asList(MEDIA_SYNCABLE_CATEGORIES)); - } + final List syncCategories = new ArrayList<>(); - if (CommonUtils.isSettingsSyncEnabled(account)) { - syncCategories.addAll(Arrays.asList(SETTINGS_SYNCABLE_CATEGORIES)); - } + syncCategories.addAll(Arrays.asList(MEDIA_SYNCABLE_CATEGORIES)); + syncCategories.addAll(Arrays.asList(SETTINGS_SYNCABLE_CATEGORIES)); getInitialSyncedFolders(syncCategories); - - this.existingRemoteFolderCounter = 0; - - CreateNextRemoteFolder(); + CommonUtils.registerInitializationWorkers(syncedFolders, WorkManager.getInstance(getApplicationContext()) ); } /** @@ -142,8 +122,6 @@ public class InitializerService extends Service implements OnRemoteOperationList final String DEVICE_SPECIFIC_PATH = PATH_SEPARATOR+"Devices"+PATH_SEPARATOR+ Build.BRAND+"_"+ Build.MODEL+"_" + Build.SERIAL; switch (categories.get(i)) { - case "Medias": - break; case "Images": syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_DCIM), "/Photos/", true)); @@ -171,6 +149,7 @@ public class InitializerService extends Service implements OnRemoteOperationList "/Podcasts/", true)); break; case "Rom settings": + String remoteFolderPath = DEVICE_SPECIFIC_PATH+"/rom_settings/"; syncedFolders.add(new SyncedFolder(categories.get(i), "/data/system/users/0/", remoteFolderPath, true, false, true, false)); try{ @@ -180,7 +159,7 @@ public class InitializerService extends Service implements OnRemoteOperationList remoteFolderPath+"app_list/", true, false, - true, + CommonUtils.isSettingsSyncEnabled(account), false)); } catch (Exception e) { Log.e(TAG, e.toString()); } break; @@ -192,94 +171,9 @@ public class InitializerService extends Service implements OnRemoteOperationList return CommonUtils.getLocalPath(Environment.getExternalStoragePublicDirectory(directory))+ PATH_SEPARATOR; } - private void CreateNextRemoteFolder() { - Log.i(TAG, "createNextRemoteFolder()"); - this.restartFolderCreationCounter = 0; - - if (this.syncedFolders == null || this.syncedFolders.isEmpty()) { - this.stopSelf(); - } - - //It means that there are still folders to create - if (this.existingRemoteFolderCounter < this.syncedFolders.size()) { - if (this.handler == null) this.handler = new Handler(); - - CreateInitialFolderRemoteOperation createFolderOperation = - new CreateInitialFolderRemoteOperation( - this.syncedFolders.get(this.existingRemoteFolderCounter), - true, - this); - - createFolderOperation.execute(this.cloudClient, this, this.handler); - - } else if (this.existingRemoteFolderCounter == this.syncedFolders.size()) { - doLastStep(); - } else { - Log.e(TAG, "this.existingRemoteFolderCounter : "+this.existingRemoteFolderCounter+" > this.mSyncedFolders.size() : "+this.syncedFolders.size()); - this.stopSelf(); - } - } - - @Override - public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { - Log.i(TAG, "onRemoteOperationFinish()"); - if (operation instanceof CreateInitialFolderRemoteOperation) { - - if (result.isSuccess() || result.getCode() == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS) { - this.existingRemoteFolderCounter+=1; - CreateNextRemoteFolder(); - - } else if (result.getHttpCode() == 423 || result.getHttpCode() == 409) {//file locked or conflict in result - Log.e(TAG, result.getLogMessage()); - - if (this.restartFolderCreationCounter < 3) { - Log.w(TAG, " restart operation"); - operation.execute(this.cloudClient, this, this.handler); - this.restartFolderCreationCounter+=1; - } else { - Log.e(TAG, "Remote folder's creation failed due to conflict with server"); - stopSelf(); - } - } else { - Log.e(TAG, result.getLogMessage()+" "+result.getHttpCode()); - stopSelf(); - } - } - } - - - /** - * Function to check if all remote folder have been created - **/ - private void doLastStep() { - Log.i(TAG, "doLastStep()"); - final Context appContext = getApplicationContext(); - appContext.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, - Context.MODE_PRIVATE) - .edit() - .putBoolean(AppConstants.INITIALIZATION_HAS_BEEN_DONE, true) - .putInt(INITIALFOLDERS_NUMBER, syncedFolders.size()) - .apply(); - - CommonUtils.registerPeriodicFullScanWorker(WorkManager.getInstance(appContext)); - - //all folder have been created - ((EdriveApplication) this.getApplication()).startRecursiveFileObserver(); - - Intent SynchronizationServiceIntent = new Intent(getApplicationContext(), foundation.e.drive.services.SynchronizationService.class); - startService(SynchronizationServiceIntent); - - //Immediatly start ObserverService to not have to wait 30 minutes. - Intent observersServiceIntent = new Intent(getApplicationContext(), foundation.e.drive.services.ObserverService.class); - startService(observersServiceIntent); - - stopSelf(); - } - @Override public void onDestroy() { super.onDestroy(); - this.handler = null; this.account = null; this.cloudClient = null; if (this.syncedFolders != null) this.syncedFolders.clear(); 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 4c3be31d..fec0762d 100644 --- a/app/src/main/java/foundation/e/drive/utils/AppConstants.java +++ b/app/src/main/java/foundation/e/drive/utils/AppConstants.java @@ -34,11 +34,11 @@ public abstract class AppConstants { 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"}; - public final static String notificationChannelID ="3310"; - public final static String notificationChannelName="eDrive channel"; - public final static String WORK_GENERIC_TAG="eDrive"; - - public final static String USER_AGENT = "eos("+getBuildTime()+")-eDrive("+ BuildConfig.VERSION_NAME +")"; + public static final String notificationChannelID ="3310"; + public static final String notificationChannelName="eDrive channel"; + public static final String WORK_GENERIC_TAG="eDrive"; + public static final String WORK_INITIALIZATION_TAG="eDrive-init"; + public static final String USER_AGENT = "eos("+getBuildTime()+")-eDrive("+ BuildConfig.VERSION_NAME +")"; /** * Get a readable OS's build date String 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 a7fcdf0c..2cab9888 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -17,9 +17,7 @@ import android.content.ContentResolver; import android.content.Context; import android.media.MediaScannerConnection; import android.net.ConnectivityManager; -import android.net.Network; import android.net.NetworkCapabilities; -import android.net.NetworkInfo; import android.net.Uri; import android.util.Log; import android.webkit.MimeTypeMap; @@ -32,18 +30,36 @@ import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.resources.files.FileUtils; import java.io.File; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; +import foundation.e.drive.models.SyncedFolder; +import foundation.e.drive.work.CreateRemoteFolderWorker; +import foundation.e.drive.work.FirstStartWorker; import foundation.e.drive.work.FullScanWorker; import static foundation.e.drive.utils.AppConstants.MEDIASYNC_PROVIDER_AUTHORITY; import static foundation.e.drive.utils.AppConstants.SETTINGSYNC_PROVIDER_AUTHORITY; +import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_ENABLE; +import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_ID; +import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_LAST_ETAG; +import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_LAST_MODIFIED; +import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_LIBELLE; +import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_LOCAL_PATH; +import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_MEDIATYPE; +import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_REMOTE_PATH; +import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_SCAN_LOCAL; +import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_SCAN_REMOTE; import androidx.annotation.NonNull; +import androidx.work.BackoffPolicy; import androidx.work.Constraints; +import androidx.work.Data; import androidx.work.ExistingPeriodicWorkPolicy; import androidx.work.NetworkType; +import androidx.work.OneTimeWorkRequest; import androidx.work.PeriodicWorkRequest; import androidx.work.WorkManager; @@ -304,4 +320,73 @@ public abstract class CommonUtils { workManager.enqueueUniquePeriodicWork(FullScanWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.KEEP, periodicFullScanRequest); } + + + /** + * This method create a chain of WorkRequests to perform Initialization tasks: + * Firstly, it creates WorkRequest to create remote root folders on ecloud + * Then, once all folders are present on cloud, run the FirstStartWorker. + * + * in details: + * - Create 9 remote folders on ecloud + * - Run a first fullscan (ObserverService) + * - start SynchronizationService + * - Active FileObserver + * - Schedule periodic fullscan. + * + * @param syncedFolders List of SyncedFolder for which we want to create a remote folder on ecloud + * @param workManager WorkManager instance to register WorkRequest + */ + public static void registerInitializationWorkers(List syncedFolders, WorkManager workManager){ + if (syncedFolders == null || syncedFolders.isEmpty()) { + Log.e(TAG, "Can't create remote folders. List is empty"); + return; + } + + final Constraints constraints = new Constraints.Builder() + .setRequiredNetworkType(NetworkType.UNMETERED) + .setRequiresBatteryNotLow(true) + .build(); + + final List workRequests = new ArrayList<>(); + + for (SyncedFolder folder : syncedFolders) { + final OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder( + CreateRemoteFolderWorker.class) + .setBackoffCriteria(BackoffPolicy.LINEAR, 2, TimeUnit.MINUTES) + .setInputData(createDataFromSyncedFolder(folder)) + .addTag(AppConstants.WORK_GENERIC_TAG) + .addTag(AppConstants.WORK_INITIALIZATION_TAG) + .setConstraints(constraints) + .build(); + + workRequests.add(oneTimeWorkRequest); + } + + final OneTimeWorkRequest firstStartRequest = new OneTimeWorkRequest.Builder(FirstStartWorker.class) + .setBackoffCriteria(BackoffPolicy.LINEAR, 2, TimeUnit.MINUTES) + .addTag(AppConstants.WORK_GENERIC_TAG) + .addTag(AppConstants.WORK_INITIALIZATION_TAG) + .build(); + + workManager.beginWith(workRequests) + .then(firstStartRequest) + .enqueue(); + } + + + private static Data createDataFromSyncedFolder(SyncedFolder folder) { + return new Data.Builder() + .putInt(DATA_KEY_ID, folder.getId()) + .putString(DATA_KEY_LIBELLE, folder.getLibelle()) + .putString(DATA_KEY_LOCAL_PATH, folder.getLocalFolder()) + .putString(DATA_KEY_REMOTE_PATH, folder.getRemoteFolder()) + .putString(DATA_KEY_LAST_ETAG, folder.getLastEtag()) + .putLong(DATA_KEY_LAST_MODIFIED, folder.getLastModified()) + .putBoolean(DATA_KEY_SCAN_LOCAL, folder.isScanLocal()) + .putBoolean(DATA_KEY_SCAN_REMOTE, folder.isScanRemote()) + .putBoolean(DATA_KEY_ENABLE, folder.isEnabled()) + .putBoolean(DATA_KEY_MEDIATYPE, folder.isMediaType()) + .build(); + } } \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/work/CreateRemoteFolderWorker.java b/app/src/main/java/foundation/e/drive/work/CreateRemoteFolderWorker.java new file mode 100644 index 00000000..8c1443e2 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/work/CreateRemoteFolderWorker.java @@ -0,0 +1,127 @@ +/* + * Copyright © Vincent Bourgmayer (/e/ foundation). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package foundation.e.drive.work; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.work.Data; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation; + +import java.io.File; + +import foundation.e.drive.database.DbHelper; +import foundation.e.drive.models.SyncedFolder; +import foundation.e.drive.utils.AppConstants; +import foundation.e.drive.utils.CommonUtils; + +/** + * Create folder on ecloud for a given local folder + * @author Vincent Bourgmayer + */ +public class CreateRemoteFolderWorker extends Worker { + private static final String TAG = CreateRemoteFolderWorker.class.getSimpleName(); + public static final String DATA_KEY_ID="id"; + public static final String DATA_KEY_LIBELLE="libelle"; + public static final String DATA_KEY_LOCAL_PATH="localPath"; + public static final String DATA_KEY_REMOTE_PATH="remotePath"; + public static final String DATA_KEY_LAST_ETAG="etag"; + public static final String DATA_KEY_LAST_MODIFIED="lModified"; + public static final String DATA_KEY_SCAN_LOCAL="scanLocal"; + public static final String DATA_KEY_SCAN_REMOTE="scanRemote"; + public static final String DATA_KEY_ENABLE="enable"; + public static final String DATA_KEY_MEDIATYPE="mediaType"; + + public CreateRemoteFolderWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + + @NonNull + @Override + public Result doWork() { + final Context context = getApplicationContext(); + if (!CommonUtils.haveNetworkConnection(context)) { + Log.e(TAG, "Can't create remote folder because there is no unmetered connexion"); + return Result.retry(); + } + + final Account account = getAccount(); + if (account == null) { + Log.e(TAG, "Can't get valid account: stop everything"); + return Result.failure(); + } + + final SyncedFolder syncedFolder = getSyncedFolderFromData(); + Log.d(TAG, "doWork() for :"+syncedFolder.getLocalFolder()); + final File folder = new File(syncedFolder.getLocalFolder() ); + if (!folder.exists()) { + folder.mkdirs(); + syncedFolder.setLastModified(folder.lastModified()); + } + + final OwnCloudClient client = CommonUtils.getOwnCloudClient(account, context); + if (client == null) { + Log.e(TAG, "Can't get OwnCloudClient."); + return Result.retry(); + } + + 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(); + } + } catch (Exception e) { + Log.e(TAG, "Exception: "+e.getClass().getSimpleName()); + } + return Result.retry(); + } + + private SyncedFolder getSyncedFolderFromData() { + Data data = getInputData(); + SyncedFolder result = new SyncedFolder( + data.getString(DATA_KEY_LIBELLE), + data.getString(DATA_KEY_LOCAL_PATH), + data.getString(DATA_KEY_REMOTE_PATH), + data.getBoolean(DATA_KEY_SCAN_LOCAL, true), + data.getBoolean(DATA_KEY_SCAN_REMOTE, true), + data.getBoolean(DATA_KEY_ENABLE, true), + data.getBoolean(DATA_KEY_MEDIATYPE, true)); + + result.setLastModified(data.getLong(DATA_KEY_LAST_MODIFIED, 0L)); + result.setLastEtag(data.getString(DATA_KEY_LAST_ETAG)); + result.setId(data.getInt(DATA_KEY_ID, -1)); + + return result; + } + + + private Account getAccount() { + 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, ""); + + if (accountName.isEmpty() && accountType.isEmpty()) return null; + return CommonUtils.getAccount(accountName, accountType, AccountManager.get(getApplicationContext())); + } +} diff --git a/app/src/main/java/foundation/e/drive/work/FirstStartWorker.java b/app/src/main/java/foundation/e/drive/work/FirstStartWorker.java new file mode 100644 index 00000000..98ded192 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/work/FirstStartWorker.java @@ -0,0 +1,60 @@ +/* + * Copyright © Vincent Bourgmayer (/e/ foundation). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package foundation.e.drive.work; + +import static foundation.e.drive.utils.AppConstants.INITIALFOLDERS_NUMBER; + +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.work.WorkManager; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import foundation.e.drive.EdriveApplication; +import foundation.e.drive.utils.AppConstants; +import foundation.e.drive.utils.CommonUtils; + +/** + * This class start eDrive work after initialization. + * It contains job to start FileObserver, periodic fullScan and Synchronization service + * for the first time + * @author Vincent Bourgmayer + */ +public class FirstStartWorker extends Worker { + private static final String TAG = FirstStartWorker.class.getSimpleName(); + public FirstStartWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + + @NonNull + @Override + public Result doWork() { + Log.d(TAG, "doWork()"); + final Context appContext = getApplicationContext(); + appContext.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, + Context.MODE_PRIVATE) + .edit() + .putBoolean(AppConstants.INITIALIZATION_HAS_BEEN_DONE, true) + .putInt(INITIALFOLDERS_NUMBER, 9) + .apply(); + + CommonUtils.registerPeriodicFullScanWorker(WorkManager.getInstance(appContext)); + + getApplicationContext().startService(new Intent(getApplicationContext(), foundation.e.drive.services.SynchronizationService.class)); + getApplicationContext().startService(new Intent(getApplicationContext(), foundation.e.drive.services.ObserverService.class)); + + //all folder have been created + ((EdriveApplication) getApplicationContext()).startRecursiveFileObserver(); + + return Result.success(); + } +} -- GitLab From 2e5da77c6071d071a94265a89bcb73852cd74e2f Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 5 Apr 2022 11:04:12 +0200 Subject: [PATCH 17/25] Create NotificationChannel when SDK is >= Oreo Add method to create notificationChannel in CommonUtils and add few strings (in res/values folder) for that purpose --- .../foundation/e/drive/EdriveApplication.java | 5 +++++ .../foundation/e/drive/utils/AppConstants.java | 3 +-- .../foundation/e/drive/utils/CommonUtils.java | 18 ++++++++++++++++++ app/src/main/res/values/strings.xml | 4 ++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/EdriveApplication.java b/app/src/main/java/foundation/e/drive/EdriveApplication.java index fcb1a0f8..4551cbe2 100644 --- a/app/src/main/java/foundation/e/drive/EdriveApplication.java +++ b/app/src/main/java/foundation/e/drive/EdriveApplication.java @@ -11,9 +11,12 @@ package foundation.e.drive; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Application; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.os.Build; import android.os.Environment; import android.util.Log; @@ -43,11 +46,13 @@ public class EdriveApplication extends Application { mFileObserver = new RecursiveFileObserver(getApplicationContext(), pathForObserver, fileEventListener); SharedPreferences prefs = getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + CommonUtils.createNotificationChannel(getApplicationContext()); if (prefs.getString(AccountManager.KEY_ACCOUNT_NAME, null) != null) { Log.d(TAG, "Account already registered"); startRecursiveFileObserver(); + Intent SynchronizationServiceIntent = new Intent(getApplicationContext(), SynchronizationService.class); startService(SynchronizationServiceIntent); 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 fec0762d..63f00e48 100644 --- a/app/src/main/java/foundation/e/drive/utils/AppConstants.java +++ b/app/src/main/java/foundation/e/drive/utils/AppConstants.java @@ -34,8 +34,7 @@ public abstract class AppConstants { 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"}; - public static final String notificationChannelID ="3310"; - public static final String notificationChannelName="eDrive channel"; + public static final String notificationChannelID ="foundation.e.drive"; public static final String WORK_GENERIC_TAG="eDrive"; public static final String WORK_INITIALIZATION_TAG="eDrive-init"; public static final String USER_AGENT = "eos("+getBuildTime()+")-eDrive("+ BuildConfig.VERSION_NAME +")"; 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 2cab9888..563841fd 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -12,6 +12,8 @@ package foundation.e.drive.utils; import android.accounts.Account; import android.accounts.AccountManager; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.app.Service; import android.content.ContentResolver; import android.content.Context; @@ -19,6 +21,7 @@ import android.media.MediaScannerConnection; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.Uri; +import android.os.Build; import android.util.Log; import android.webkit.MimeTypeMap; @@ -35,6 +38,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; +import foundation.e.drive.R; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.work.CreateRemoteFolderWorker; import foundation.e.drive.work.FirstStartWorker; @@ -389,4 +393,18 @@ public abstract class CommonUtils { .putBoolean(DATA_KEY_MEDIATYPE, folder.isMediaType()) .build(); } + + public static void createNotificationChannel(Context context){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + final CharSequence name = context.getString(R.string.notif_channel_name); + final String description = context.getString(R.string.notif_channel_description); + int importance = NotificationManager.IMPORTANCE_DEFAULT; + final NotificationChannel channel = new NotificationChannel(AppConstants.notificationChannelID, name, importance); + channel.setDescription(description); + // Register the channel with the system; you can't change the importance + // or other notification behaviors after this + final NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8f9814a8..54ee1424 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,8 @@ /e/ Drive e.foundation.webdav.eelo + eDrive\'s notification + eDrive\'s notification channel + Drive Quota Exceeded + eDrive\'s notification channel -- GitLab From d99ca938b7aef52c6f01ff987826319cfa72337c Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 5 Apr 2022 11:41:49 +0200 Subject: [PATCH 18/25] read user's relative quota before to upload a file --- .../foundation/e/drive/operations/UploadFileOperation.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 ff3f00c8..248c6dda 100644 --- a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java @@ -85,10 +85,13 @@ public class UploadFileOperation extends RemoteOperation { return new RemoteOperationResult(ResultCode.SYNC_CONFLICT); } + Double relativeQuotaBeforeFileUpload = 0.0; 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(); } } @@ -155,6 +158,7 @@ public class UploadFileOperation extends RemoteOperation { ArrayList datas = new ArrayList<>(); datas.add(syncedState.getSyncedFolderId()); + datas.add(relativeQuotaBeforeFileUpload); final RemoteOperationResult finalResult = new RemoteOperationResult(resultCode); finalResult.setData(datas); return finalResult; @@ -194,6 +198,8 @@ public class UploadFileOperation extends RemoteOperation { 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 new RemoteOperationResult(ResultCode.OK); } } else { -- GitLab From e3c27fff185329a2fa647a7558e4263c714c148e Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 5 Apr 2022 12:11:30 +0200 Subject: [PATCH 19/25] Synchronization service create notification for quota usage --- app/src/main/AndroidManifest.xml | 2 + .../services/SynchronizationService.java | 46 +++++++++++++------ app/src/main/res/values/strings.xml | 8 +++- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 29b9ba12..d2d8002a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,6 +17,8 @@ http://www.gnu.org/licenses/gpl.html + + = 99.0) { + addNotification(getString(R.string.notif_quota_nearlyReached_title), getString(R.string.notif_quota_99Plus_text)); + } else if (relativeQuota >= 90.0) { + addNotification(getString(R.string.notif_quota_nearlyReached_title), getString(R.string.notif_quota_90Plus_text)); + } else if (relativeQuota >= 80.0) { + addNotification(getString(R.string.notif_quota_nearlyReached_title), getString(R.string.notif_quota_80Plus_text)); + } + } switch (result.getCode()) { case OK: Log.d(TAG, operationClassName + " Succeed"); @@ -187,19 +200,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation case QUOTA_EXCEEDED: //Case specific to UploadFileOperation Log.w(TAG, "Quota_EXCEEDED"); - - NotificationManager nM = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); - - Notification notif = new Notification.Builder(this, AppConstants.notificationChannelID) - .setContentIntent(PendingIntent.getActivity(getApplicationContext(), - 0, - new Intent(Intent.ACTION_VIEW, client.getBaseUri()), - 0)) - .setContentText("Your drive lacks of space. Tap to check " + client.getBaseUri()) - .setSmallIcon(android.R.drawable.stat_sys_warning) - .build(); - - nM.notify(1,notif ); + addNotification(getString(R.string.notif_quota_execeeded_title), getString(R.string.notif_quota_execeeded_text)); break; case FILE_NOT_FOUND: //Case specific to DownloadFileOperation @@ -233,6 +234,25 @@ public class SynchronizationService extends Service implements OnRemoteOperation return operation; } + /** + * send notification to inform user that he lacks space on ecloud + * Improvement idea: + * - add translatable message & title + * - make message & title to be a parameter of the method, so we could reuse the function somewhere + * - else with different notification. File conflict for example. + **/ + private void addNotification(String title, String text){ + final NotificationCompat.Builder builder = + new NotificationCompat.Builder(this, AppConstants.notificationChannelID) + .setSmallIcon(android.R.drawable.stat_sys_warning) + .setContentTitle(title) + .setContentText(text+ client.getBaseUri()); + // Add as notification + final NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + manager.notify(0, builder.build()); + } + + /** * Handler for the class diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 54ee1424..4ba1b935 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,6 +3,10 @@ e.foundation.webdav.eelo eDrive\'s notification eDrive\'s notification channel - Drive Quota Exceeded - eDrive\'s notification channel + /e/ account\'s quota reached + /e/ account\'s quota nearly reached + eDrive can\'t upload a file that is bigger than remaining quota at: + You filled 99% of your /e/\'s account quota. check: + You filled 90% of your /e/\'s account quota. check: + You filled 80% of your /e/\'s account quota. check: -- GitLab From 47d0d57a9bd2883dde76d1591552ba33dc0f2da5 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 5 Apr 2022 12:21:34 +0200 Subject: [PATCH 20/25] add missing dependency for notification --- app/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle b/app/build.gradle index 018d91fd..e1ec7cf1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -60,6 +60,7 @@ dependencies { api project(':NextcloudLib') implementation fileTree(include: ['*.jar'], dir: 'libs') api 'androidx.annotation:annotation:1.3.0' + implementation 'androidx.core:core:1.6.0' def work_version = "2.7.1" // (Java only) -- GitLab From 56f1075759f2b5c0f188347080d744641a766daf Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 5 Apr 2022 15:23:55 +0200 Subject: [PATCH 21/25] UploadFileOperation: fix quota checking --- .../java/foundation/e/drive/operations/UploadFileOperation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 248c6dda..48826b7c 100644 --- a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java @@ -200,7 +200,7 @@ public class UploadFileOperation extends RemoteOperation { Log.d(TAG, "Quota Okay"); RemoteOperationResult result = new RemoteOperationResult(ResultCode.OK); result.setSingleData((Double) userInfo.getQuota().getRelative()); - return new RemoteOperationResult(ResultCode.OK); + return result; } } else { Log.w(TAG, "getRemoteUserInfoOperation failed: "+ocsResult.getHttpCode() ); -- GitLab From 65c114d7377ab3bf4a62dc237fce460ab270089b Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Tue, 5 Apr 2022 13:25:32 +0000 Subject: [PATCH 22/25] Apply 1 suggestion(s) to 1 file(s) --- .../java/foundation/e/drive/operations/UploadFileOperation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 48826b7c..e0171369 100644 --- a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java @@ -90,7 +90,7 @@ public class UploadFileOperation extends RemoteOperation { RemoteOperationResult checkQuotaResult = checkAvailableSpace(client, file.length()); if (checkQuotaResult.getCode() != ResultCode.OK) { return new RemoteOperationResult(checkQuotaResult.getCode()); - }else{ + } else { relativeQuotaBeforeFileUpload = (Double) checkQuotaResult.getSingleData(); } } -- GitLab From 7de4135428109edb7b871970e2d97bba2ec32a85 Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Tue, 5 Apr 2022 13:34:03 +0000 Subject: [PATCH 23/25] Apply 1 suggestion(s) to 1 file(s) --- app/src/main/java/foundation/e/drive/EdriveApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/foundation/e/drive/EdriveApplication.java b/app/src/main/java/foundation/e/drive/EdriveApplication.java index 4551cbe2..a8f39f23 100644 --- a/app/src/main/java/foundation/e/drive/EdriveApplication.java +++ b/app/src/main/java/foundation/e/drive/EdriveApplication.java @@ -52,7 +52,6 @@ public class EdriveApplication extends Application { Log.d(TAG, "Account already registered"); startRecursiveFileObserver(); - Intent SynchronizationServiceIntent = new Intent(getApplicationContext(), SynchronizationService.class); startService(SynchronizationServiceIntent); -- GitLab From c5f2c70a91759fa957fe418292833403cc6b6f59 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 5 Apr 2022 15:59:16 +0200 Subject: [PATCH 24/25] fix parsing for user quota --- .../foundation/e/drive/operations/UploadFileOperation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 48826b7c..aa4acf55 100644 --- a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java @@ -85,13 +85,13 @@ public class UploadFileOperation extends RemoteOperation { return new RemoteOperationResult(ResultCode.SYNC_CONFLICT); } - Double relativeQuotaBeforeFileUpload = 0.0; + 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(); + relativeQuotaBeforeFileUpload = ((Double) checkQuotaResult.getSingleData()).floatValue(); } } -- GitLab From a18048cfd2a16118865437635be2bc0fe1d87d66 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 5 Apr 2022 16:04:50 +0200 Subject: [PATCH 25/25] refix space around else statement --- .../java/foundation/e/drive/operations/UploadFileOperation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 aa4acf55..5d972a0b 100644 --- a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java @@ -90,7 +90,7 @@ public class UploadFileOperation extends RemoteOperation { RemoteOperationResult checkQuotaResult = checkAvailableSpace(client, file.length()); if (checkQuotaResult.getCode() != ResultCode.OK) { return new RemoteOperationResult(checkQuotaResult.getCode()); - }else{ + } else { relativeQuotaBeforeFileUpload = ((Double) checkQuotaResult.getSingleData()).floatValue(); } } -- GitLab