diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7337a890ac65106bb8dcaeac04f4eb87f89869c3..29b9ba121d55c864aa4496b16dfe80dff8b48ab3 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 c523f54c61328d9a37a655acad1f50e558dd6c4b..0000000000000000000000000000000000000000 --- 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 3776c6e7de6003b3b6435cfeb7948f019b84dcb8..99aad5fe4f0b0618cbbdbb6f73c54616d4945838 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 0000000000000000000000000000000000000000..d532786e5599b196184d512fc7326d01116c35f5 --- /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 250e791b2f7bbc9bf706fa16fc423f32c3499e8f..d94e1c21320140d7a649c689c50d134ad322feb3 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 1c01da6f62b5e358f9ca67a8150611380e996e57..fbf30d4f08ea23ae20d7b688d4e367693a391e14 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 0a2fb8f73f1790b30fd3d68758ec7f818d3d7c35..0000000000000000000000000000000000000000 --- 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); - - } - - -}