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 68b1a71de9bbcaeb8d2475813b42a3ccf4317021..7104feb8ea65ef3be48beb5363aae3c8c5a156d2 100644 --- a/app/src/main/java/foundation/e/drive/models/SyncedFileState.java +++ b/app/src/main/java/foundation/e/drive/models/SyncedFileState.java @@ -23,7 +23,6 @@ public class SyncedFileState implements Parcelable { 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; private String name; //name of the file private String localPath; //Path on the device file system @@ -144,6 +143,14 @@ public class SyncedFileState implements Parcelable { return (this.lastETAG != null && !this.lastETAG.isEmpty() ); } + /** + * Determine in it has already been synchronized once. + * @return true if contains data for both local (local last modified) & remote file (eTag) + */ + public boolean hasBeenSynchronizedOnce() { + return this.isLastEtagStored() && this.getLocalLastModified() > 0L; + } + /** * Get the syncedFolder _id * @return long 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 100fef877697548d31329f1c0f73173ed6628a11..589766be51a4f960b98de40a344c0f00f61a5451 100644 --- a/app/src/main/java/foundation/e/drive/services/ObserverService.java +++ b/app/src/main/java/foundation/e/drive/services/ObserverService.java @@ -9,6 +9,10 @@ package foundation.e.drive.services; +import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; +import static foundation.e.drive.utils.AppConstants.INITIALIZATION_HAS_BEEN_DONE; +import static foundation.e.drive.utils.FileDiffUtils.getActionForFileDiff; + import android.accounts.Account; import android.accounts.AccountManager; import android.app.Service; @@ -20,12 +24,16 @@ import android.os.Handler; import android.os.IBinder; import android.provider.MediaStore; 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 com.owncloud.android.lib.resources.files.FileUtils; import com.owncloud.android.lib.resources.files.model.RemoteFile; + import java.io.File; import java.io.FileFilter; import java.io.FileOutputStream; @@ -40,23 +48,18 @@ import foundation.e.drive.fileFilters.FileFilterFactory; import foundation.e.drive.fileFilters.OnlyFileFilter; import foundation.e.drive.models.DownloadRequest; import foundation.e.drive.models.SyncRequest; -import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.models.SyncedFileState; +import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.operations.ListFileRemoteOperation; import foundation.e.drive.receivers.DebugCmdReceiver; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; +import foundation.e.drive.utils.FileDiffUtils; import foundation.e.drive.utils.FileDiffUtils.Action; 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; -import static foundation.e.drive.utils.FileDiffUtils.getActionForFileDiff; - -import androidx.annotation.Nullable; - /** * @author Vincent Bourgmayer * @author Nicolas Gelot @@ -433,7 +436,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene if ( mSyncedFolders.get(j).getRemoteFolder().equals( parentOfKnownPath ) ) { //We have found the parent folder final SyncedFolder parentFolder = mSyncedFolders.get(j); - String fileName = CommonUtils.getFileNameFromPath(remoteFilePath); //get remote file's name + final String fileName = CommonUtils.getFileNameFromPath(remoteFilePath); if (fileName != null) { int scannableValue = 0; @@ -479,38 +482,36 @@ 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() ) ){ - Log.d(TAG, "Sync of current file: "+syncedFileState.getName()+" isn't allowed"); + + final SyncedFileState syncedFileState = syncedFileStates.get(i); + + 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) { - - //Get local file - File file = new File( syncedFileStates.get(i).getLocalPath() ); - - //Try to remove local file - boolean fileExists = file.exists(); - if ( fileExists) { - Log.d(TAG, file.getName()+" exists *1"); - //delete file - int rowAffected = getContentResolver().delete(MediaStore.Files.getContentUri("external"), - MediaStore.Files.FileColumns.DATA + "=?", - new String[]{CommonUtils.getLocalPath(file)}); - Log.d(TAG, "deleted rows by mediastore : "+rowAffected); - //sometimes (it seems to be relative to file's type) mediastore don't remove local file from storage - fileExists = !file.delete(); + if (!syncedFileState.hasBeenSynchronizedOnce()) { + continue; + } + + final File file = new File(syncedFileStates.get(i).getLocalPath()); + + //Try to remove local file + if (file.exists()) { + int rowAffected = getContentResolver().delete(MediaStore.Files.getContentUri("external"), + MediaStore.Files.FileColumns.DATA + "=?", + new String[]{CommonUtils.getLocalPath(file)}); + Log.d(TAG, "deleted rows by mediastore : " + rowAffected); + + if (!file.delete()) { //May throw SecurityException or IOException + Log.w(TAG, "local file (" + file.getName() + ") removal failed."); + continue; } + } - //if it succeed, remove syncedFileState in DB - 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 - Log.w(TAG, "local file:"+file.getName()+" still exist and can't be remove"); + if (DbHelper.manageSyncedFileStateDB(syncedFileState, "DELETE", this) <= 0) { + Log.e(TAG, "Failed to remove " + file.getName() + " from DB"); } } } @@ -563,10 +564,10 @@ public class ObserverService extends Service implements OnRemoteOperationListene if (CommonUtils.isSettingsSyncEnabled(mAccount)) generateAppListFile(); - ListIterator iterator = mSyncedFolders.listIterator() ; + final ListIterator iterator = mSyncedFolders.listIterator() ; //Loop through folders - while(iterator.hasNext() ){ - SyncedFolder syncedFolder = iterator.next(); + while(iterator.hasNext()) { + final SyncedFolder syncedFolder = iterator.next(); Log.d(TAG, "SyncedFolder :"+syncedFolder.getLibelle()+", "+syncedFolder.getLocalFolder()+", "+syncedFolder.getLastModified()+", "+syncedFolder.isScanLocal()+", "+syncedFolder.getId() ); //Check it's not a hidden file @@ -669,26 +670,24 @@ public class ObserverService extends Service implements OnRemoteOperationListene private void handleLocalFiles(List localFileList, List syncedFileStates ){ Log.i(TAG, "handleLocalFiles()"); Log.d(TAG, "Loop through local file list"); - Log.v(TAG, "format: filePath, exist, lastModified) :"); //Loop through local files - for(int i =-1, localFilesSize = localFileList.size(); ++i < localFilesSize;){ + for (int i =-1, localFilesSize = localFileList.size(); ++i < localFilesSize;){ - File localFile = localFileList.get(i); - String filePath = CommonUtils.getLocalPath( localFile ); + final File localFile = localFileList.get(i); + final String filePath = CommonUtils.getLocalPath( localFile ); boolean correspondant_found = false; - Log.v(TAG, "Current file is "+filePath+", "+localFile.exists()+", "+localFile.lastModified() ); + Log.v(TAG, "Current file is "+filePath+", exist: "+localFile.exists()+", last modified: "+localFile.lastModified() ); - ListIterator syncedFileListIterator = syncedFileStates.listIterator(); + final ListIterator syncedFileListIterator = syncedFileStates.listIterator(); Log.d(TAG, "Loop through syncedFileStates "); - Log.v(TAG, "format: (Path, Id, last Modified)"); while( syncedFileListIterator.hasNext() ) { - SyncedFileState syncedFileState = syncedFileListIterator.next(); + final 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; } @@ -696,22 +695,18 @@ 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.i(TAG, "Add upload request for file "+syncedFileState.getId()); + if (FileDiffUtils.getActionForFileDiff(localFile, syncedFileState) == Action.Upload) { 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"); //Extract parent path from knownPath @@ -727,7 +722,7 @@ public class ObserverService extends Service implements OnRemoteOperationListene } //create the syncedFile State - SyncedFileState newSyncedFileState = new SyncedFileState(-1, localFile.getName(), filePath, syncedFolder.getRemoteFolder() + localFile.getName(), "", 0, syncedFolder.getId(), syncedFolder.isMediaType(),scannableValue); + final 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); @@ -752,19 +747,21 @@ public class ObserverService extends Service implements OnRemoteOperationListene */ private void handleLocalRemainingSyncedFileState(List syncedFileStates){ Log.i(TAG, "handleLocalRemainingSyncedFileState(...)"); - //Loop through remaining SyncedFileState - for(SyncedFileState fileState : syncedFileStates){ - 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()){ - Log.w(TAG, "The file still exist. There is a problem!"); - } else { - Log.i(TAG, "Add remote remove request for file "+fileState.getId()); - this.syncRequests.put(fileState.getId(), new SyncRequest(fileState, SyncRequest.Type.REMOTE_DELETE)); - } + + for(SyncedFileState fileState : syncedFileStates) { + if (!fileState.hasBeenSynchronizedOnce()) { + continue; } + + final File file = new File(fileState.getLocalPath()); + + if (file.exists()) { + Log.w(TAG, file.getAbsolutePath() + "The file still exist. There is a problem!"); + continue; + } + + Log.i(TAG, "Add remove SyncRequest for file " + file.getAbsolutePath()); + this.syncRequests.put(fileState.getId(), new SyncRequest(fileState, SyncRequest.Type.REMOTE_DELETE)); } } /* end of methods related to device Scanning */ diff --git a/app/src/main/java/foundation/e/drive/utils/FileDiffUtils.java b/app/src/main/java/foundation/e/drive/utils/FileDiffUtils.java index 1edaa713f3434537216ac0028ef454233348f44b..2880ec2ab1661dc9d083c8e395275c8948fe1bac 100644 --- a/app/src/main/java/foundation/e/drive/utils/FileDiffUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/FileDiffUtils.java @@ -7,7 +7,6 @@ */ package foundation.e.drive.utils; - import com.owncloud.android.lib.resources.files.model.RemoteFile; import java.io.File; @@ -24,12 +23,10 @@ public class FileDiffUtils { public enum Action { Upload, Download, - Remove, skip, updateDB } - /** * Define what to do of RemoteFile for which we know the Database equivalent * @param remoteFile RemoteFile @@ -50,6 +47,21 @@ public class FileDiffUtils { return Action.Download; } + + /** + * Define what to do of local file for which we know the Database equivalent + * @param localFile File instance representing a file on the device + * @param fileState SyncedFileState instance. Containing data from Database + * @return Action from Enum + */ + public static Action getActionForFileDiff(File localFile, SyncedFileState fileState) { + //If no etag is stored in sfs, the file hasn't been sync up to server. then do upload + if (fileState.getLocalLastModified() < localFile.lastModified() || !fileState.isLastEtagStored()) { + return Action.Upload; + } + return Action.skip; + } + /** * Compare RemoteFile's eTag with the one stored in Database * @param file RemoteFile @@ -68,7 +80,7 @@ public class FileDiffUtils { * @return true if localLastModified store in Database == 0 */ private static boolean hasAlreadyBeenDownloaded(SyncedFileState fileState) { - return fileState.getLocalLastModified() == 0l; + return fileState.getLocalLastModified() == 0L; } /**