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 6cec272c34989bab085eec4a9f2a68c3614deb82..2d724ef838d8b95c28001351dc1bc2ac55b8414b 100644 --- a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java +++ b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java @@ -10,9 +10,9 @@ package foundation.e.drive.FileObservers; import static foundation.e.drive.models.SyncRequest.Type.DISABLE_SYNCING; import static foundation.e.drive.models.SyncRequest.Type.UPLOAD; -import static foundation.e.drive.models.SyncedFileState.DEVICE_SCANNABLE; -import static foundation.e.drive.models.SyncedFileState.ECLOUD_SCANNABLE; -import static foundation.e.drive.models.SyncedFileState.NOT_SCANNABLE; +import static foundation.e.drive.models.SyncedFileStateKt.DO_NOT_SCAN; +import static foundation.e.drive.models.SyncedFileStateKt.SCAN_ON_CLOUD; +import static foundation.e.drive.models.SyncedFileStateKt.SCAN_ON_DEVICE; import static foundation.e.drive.utils.FileUtils.getLocalPath; import android.content.Context; @@ -103,7 +103,7 @@ public class FileEventListener { /** * Send syncRequest to SynchronizationService - * @param request + * @param request SyncRequest that should be executed asap */ private void sendSyncRequestToSynchronizationService(@NonNull SyncRequest request) { Timber.d("Sending a SyncRequest for %s", request.getSyncedFileState().getName()); @@ -121,11 +121,14 @@ public class FileEventListener { /** * When a new directory is detected, it must be inserted in database * if it's parent directory is already in the database - * @param directory + * @param directory Directory that has been created */ private void handleDirectoryCreate(@NonNull File directory) { Timber.d("handleDirectoryCreate( %s )",directory.getAbsolutePath()); - final String parentPath = getLocalPath(directory.getParentFile()); + final File parentFile = directory.getParentFile(); + if (parentFile == null) return; + + final String parentPath = getLocalPath(parentFile); final SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder != null) { final SyncedFolder folder = new SyncedFolder(parentFolder, directory.getName() + PATH_SEPARATOR, directory.lastModified(), ""); @@ -136,7 +139,7 @@ public class FileEventListener { /** * Handle CLOSE_WRITE event for a directory * todo: check in which condition a directory can generate a close_write - * @param directory + * @param directory Directory that has been modified */ private void handleDirectoryCloseWrite(@NonNull File directory) { final String fileLocalPath = getLocalPath(directory); @@ -152,7 +155,7 @@ public class FileEventListener { /** * Handle a file deletion event for a directory - * @param directory + * @param directory Directory that has been removed */ private void handleDirectoryDelete(@NonNull File directory) { final String fileLocalPath = getLocalPath(directory); @@ -179,7 +182,7 @@ public class FileEventListener { /** * handle a file close_write event for a file which is not a directory - * @param file + * @param file File that has been modified */ private void handleFileCloseWrite(@NonNull File file) { final String fileLocalPath = getLocalPath(file); @@ -188,20 +191,23 @@ public class FileEventListener { SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); if (fileState == null) { //New file discovered + final File parentFile = file.getParentFile(); + if (parentFile == null) return; + final String parentPath = getLocalPath(file.getParentFile()); SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder == null || !parentFolder.isEnabled()) { Timber.d("Won't send sync request: no parent are known for new file: %s", file.getName()); return; } - int scannableValue = NOT_SCANNABLE; + int scanScope = DO_NOT_SCAN; if (parentFolder.isEnabled()) { - if (parentFolder.isScanRemote()) scannableValue = ECLOUD_SCANNABLE; - if (parentFolder.isScanLocal()) scannableValue += DEVICE_SCANNABLE; + if (parentFolder.isScanRemote()) scanScope = SCAN_ON_CLOUD; + if (parentFolder.isScanLocal()) scanScope += SCAN_ON_DEVICE; } final String remotePath = parentFolder.getRemoteFolder()+file.getName(); - fileState = new SyncedFileState(-1, file.getName(), getLocalPath(file), remotePath, "", 0L, parentFolder.getId(), parentFolder.isMediaType(), scannableValue); + fileState = new SyncedFileState(-1, file.getName(), getLocalPath(file), remotePath, "", 0L, parentFolder.getId(), parentFolder.isMediaType(), scanScope); int storedId = DbHelper.manageSyncedFileStateDB(fileState, "INSERT", appContext); if (storedId > 0) { fileState.setId(storedId); @@ -210,8 +216,8 @@ public class FileEventListener { Timber.d("New File %s observed but impossible to insert it in DB", file.getName()); } } else { //File update - final boolean isWaitingForDownload = fileState.isLastEtagStored() && fileState.getLocalLastModified() == 0L; - if (fileState.getScannable() > ECLOUD_SCANNABLE && !isWaitingForDownload) { + final boolean isWaitingForDownload = fileState.isLastEtagStored() && fileState.getLastModified() == 0L; + if (fileState.getScanScope() > SCAN_ON_CLOUD && !isWaitingForDownload) { request = new SyncRequest(fileState, UPLOAD); } } @@ -222,7 +228,7 @@ public class FileEventListener { /** * Handle a file deletion event for a file which is not a directory - * @param file + * @param file File that has been removed */ private void handleFileDelete(@NonNull File file) { final String fileLocalPath = getLocalPath(file); @@ -233,7 +239,7 @@ public class FileEventListener { } //If already in DB - if (fileState.getScannable() > NOT_SCANNABLE) { + if (fileState.getScanScope() > DO_NOT_SCAN) { //todo: if file is already sync disabled, we should probably remove file from DB final SyncRequest disableSyncingRequest = new SyncRequest(fileState, DISABLE_SYNCING); this.sendSyncRequestToSynchronizationService(disableSyncingRequest); diff --git a/app/src/main/java/foundation/e/drive/contentScanner/AbstractContentScanner.java b/app/src/main/java/foundation/e/drive/contentScanner/AbstractContentScanner.java index bdf4606bfc617d7aa4d9d199216e879791c983df..c8b843c9d76bf22cb1db17c1b3765d86833eb883 100644 --- a/app/src/main/java/foundation/e/drive/contentScanner/AbstractContentScanner.java +++ b/app/src/main/java/foundation/e/drive/contentScanner/AbstractContentScanner.java @@ -20,6 +20,7 @@ import java.util.ListIterator; import foundation.e.drive.models.SyncRequest; import foundation.e.drive.models.SyncedFileState; +import foundation.e.drive.models.SyncedFileStateKt; import foundation.e.drive.models.SyncedFolder; /** @@ -68,13 +69,13 @@ public abstract class AbstractContentScanner { } for (SyncedFileState remainingFileState : fileStates) { - if (remainingFileState.getScannable() == SyncedFileState.NOT_SCANNABLE) { + if (remainingFileState.getScanScope() == SyncedFileStateKt.DO_NOT_SCAN) { continue; } onMissingFile(remainingFileState); } return syncRequests; - }; + } /** * Obtain the SyncedFolder for parent of file denoted by the given path diff --git a/app/src/main/java/foundation/e/drive/contentScanner/LocalContentScanner.java b/app/src/main/java/foundation/e/drive/contentScanner/LocalContentScanner.java index bd637fa63ebb53bd9ec4c333211fb283fe7c84d4..0d94ae0a86893c29f42a924c154a37cc59cc2b8d 100644 --- a/app/src/main/java/foundation/e/drive/contentScanner/LocalContentScanner.java +++ b/app/src/main/java/foundation/e/drive/contentScanner/LocalContentScanner.java @@ -7,9 +7,9 @@ */ package foundation.e.drive.contentScanner; -import static foundation.e.drive.models.SyncedFileState.DEVICE_SCANNABLE; -import static foundation.e.drive.models.SyncedFileState.ECLOUD_SCANNABLE; -import static foundation.e.drive.models.SyncedFileState.NOT_SCANNABLE; +import static foundation.e.drive.models.SyncedFileStateKt.DO_NOT_SCAN; +import static foundation.e.drive.models.SyncedFileStateKt.SCAN_ON_CLOUD; +import static foundation.e.drive.models.SyncedFileStateKt.SCAN_ON_DEVICE; import android.content.Context; @@ -59,13 +59,13 @@ public class LocalContentScanner extends AbstractContentScanner{ final SyncedFolder parentDir = getParentSyncedFolder(filePath); if (parentDir == null) return; - int scannableValue = NOT_SCANNABLE; + int scanScope = DO_NOT_SCAN; if (parentDir.isEnabled()) { - if (parentDir.isScanRemote()) scannableValue += ECLOUD_SCANNABLE; - if (parentDir.isScanLocal()) scannableValue += DEVICE_SCANNABLE; + if (parentDir.isScanRemote()) scanScope += SCAN_ON_CLOUD; + if (parentDir.isScanLocal()) scanScope += SCAN_ON_DEVICE; } - final SyncedFileState newSyncedFileState = new SyncedFileState(-1, file.getName(), filePath, parentDir.getRemoteFolder() + file.getName(), "", 0, parentDir.getId(), parentDir.isMediaType(),scannableValue); + final SyncedFileState newSyncedFileState = new SyncedFileState(-1, file.getName(), filePath, parentDir.getRemoteFolder() + file.getName(), "", 0, parentDir.getId(), parentDir.isMediaType(), scanScope); final int storedId = DbHelper.manageSyncedFileStateDB(newSyncedFileState, "INSERT", context); if (storedId > 0) { @@ -77,7 +77,7 @@ public class LocalContentScanner extends AbstractContentScanner{ @Override protected void onKnownFileFound(@NonNull File file, @NonNull SyncedFileState fileState) { - if (fileState.getScannable() == NOT_SCANNABLE) return; + if (fileState.getScanScope() == DO_NOT_SCAN) return; if (FileDiffUtils.getActionForFileDiff(file, fileState) == FileDiffUtils.Action.Upload) { Timber.d("Add upload SyncRequest for %s", file.getAbsolutePath()); @@ -95,6 +95,6 @@ public class LocalContentScanner extends AbstractContentScanner{ final String filePath = FileUtils.getLocalPath(file); final String localPath = fileState.getLocalPath(); - return localPath != null && localPath.equals(filePath); + return localPath.equals(filePath); } } \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/contentScanner/RemoteContentScanner.java b/app/src/main/java/foundation/e/drive/contentScanner/RemoteContentScanner.java index 3a7d500fa46232a20b401fe2dca04ea15954e316..0780d6a076cbc4b45d21aba64d90d15e239265fc 100644 --- a/app/src/main/java/foundation/e/drive/contentScanner/RemoteContentScanner.java +++ b/app/src/main/java/foundation/e/drive/contentScanner/RemoteContentScanner.java @@ -8,9 +8,9 @@ package foundation.e.drive.contentScanner; import static foundation.e.drive.models.SyncRequest.Type.DISABLE_SYNCING; -import static foundation.e.drive.models.SyncedFileState.DEVICE_SCANNABLE; -import static foundation.e.drive.models.SyncedFileState.ECLOUD_SCANNABLE; -import static foundation.e.drive.models.SyncedFileState.NOT_SCANNABLE; +import static foundation.e.drive.models.SyncedFileStateKt.DO_NOT_SCAN; +import static foundation.e.drive.models.SyncedFileStateKt.SCAN_ON_CLOUD; +import static foundation.e.drive.models.SyncedFileStateKt.SCAN_ON_DEVICE; import static foundation.e.drive.utils.FileDiffUtils.getActionForFileDiff; import android.content.Context; @@ -46,7 +46,7 @@ public class RemoteContentScanner extends AbstractContentScanner { @Override protected void onKnownFileFound(@NonNull RemoteFile file, @NonNull SyncedFileState fileState) { - if (fileState.getScannable() == NOT_SCANNABLE) return; + if (fileState.getScanScope() == DO_NOT_SCAN) return; final FileDiffUtils.Action action = getActionForFileDiff(file, fileState); if (action == FileDiffUtils.Action.Download) { @@ -56,7 +56,7 @@ public class RemoteContentScanner extends AbstractContentScanner { } else if (action == FileDiffUtils.Action.updateDB) { - fileState.setLastETAG(file.getEtag()); + fileState.setLastEtag(file.getEtag()); final int affectedRows = DbHelper.manageSyncedFileStateDB(fileState, "UPDATE", context); if (affectedRows == 0) Timber.d("Error while updating eTag in DB for: %s", file.getRemotePath()); @@ -70,14 +70,15 @@ public class RemoteContentScanner extends AbstractContentScanner { if (parentDir == null) return; final String fileName = FileUtils.getFileNameFromPath(remoteFilePath); + if (fileName == null) return; - int scannableValue = NOT_SCANNABLE; + int scanScope = DO_NOT_SCAN; if (parentDir.isEnabled()) { - if (parentDir.isScanRemote()) scannableValue += ECLOUD_SCANNABLE; - if (parentDir.isScanLocal()) scannableValue += DEVICE_SCANNABLE; + if (parentDir.isScanRemote()) scanScope += SCAN_ON_CLOUD; + if (parentDir.isScanLocal()) scanScope += SCAN_ON_DEVICE; } - final SyncedFileState newFileState = new SyncedFileState(-1, fileName, parentDir.getLocalFolder() + fileName, remoteFilePath, file.getEtag(), 0, parentDir.getId(), parentDir.isMediaType(), scannableValue); + final SyncedFileState newFileState = new SyncedFileState(-1, fileName, parentDir.getLocalFolder() + fileName, remoteFilePath, file.getEtag(), 0, parentDir.getId(), parentDir.isMediaType(), scanScope); final int storedId = DbHelper.manageSyncedFileStateDB(newFileState, "INSERT", context); if (storedId > 0) { @@ -99,9 +100,8 @@ public class RemoteContentScanner extends AbstractContentScanner { @Override protected boolean isFileMatchingSyncedFileState(@NonNull RemoteFile file, @NonNull SyncedFileState fileState) { - String remotePath = fileState.getRemotePath(); - - return remotePath != null && remotePath.equals(file.getRemotePath()); + final String remotePath = fileState.getRemotePath(); + return remotePath.equals(file.getRemotePath()); } @Override 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 9af36d51a57c85be9b92575a00f35ac33410dfde..df5bc02f49f439a558d28bc3027e1dba2762bd96 100644 --- a/app/src/main/java/foundation/e/drive/database/SyncedFileStateDAO.java +++ b/app/src/main/java/foundation/e/drive/database/SyncedFileStateDAO.java @@ -1,6 +1,6 @@ /* * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2022. + * Copyright © MURENA SAS 2022-2023. * 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 @@ -85,11 +85,11 @@ import timber.log.Timber; values.put( FILE_NAME, syncedFileState.getName() ); values.put( LOCAL_PATH, syncedFileState.getLocalPath() ); values.put( REMOTE_PATH, syncedFileState.getRemotePath() ); - values.put( LAST_ETAG, syncedFileState.getLastETAG() ); - values.put( LOCAL_LAST_MODIFIED, syncedFileState.getLocalLastModified() ); + values.put( LAST_ETAG, syncedFileState.getLastEtag() ); + values.put( LOCAL_LAST_MODIFIED, syncedFileState.getLastModified() ); values.put( SYNCEDFOLDER_ID, syncedFileState.getSyncedFolderId() ); values.put( IS_MEDIA_TYPE, syncedFileState.isMediaType() ? 1 : 0 ); - values.put( SCANNABLE, syncedFileState.getScannable()); + values.put( SCANNABLE, syncedFileState.getScanScope()); return values; } @@ -194,14 +194,13 @@ import timber.log.Timber; whereValue[i] = syncedFolderIds.get(i).toString(); } - final StringBuilder whereClause = new StringBuilder(); - whereClause.append(SYNCEDFOLDER_ID) - .append(" IN ") - .append(matcherList.toString() + final String whereClause = SYNCEDFOLDER_ID + + " IN " + + matcherList.toString() .replace("[", "(") - .replace("]", ")")); + .replace("]", ")"); - final Cursor cursor = mDB.query(TABLE_NAME, allColumns, whereClause.toString(), whereValue, null, null, null); + final Cursor cursor = mDB.query(TABLE_NAME, allColumns, whereClause, whereValue, null, null, null); cursor.moveToFirst(); while(!cursor.isAfterLast() ) { result.add( cursorToSyncedFileState(cursor) ); @@ -241,10 +240,10 @@ import timber.log.Timber; cursor.getString(2 ),//local path cursor.getString(3 ),//remote path cursor.getString(4 ),// last Etag - cursor.getLong(5 ),//Local last modified + cursor.getLong(5 ),//Local mTime cursor.getLong(6 ), //SyncedFolderID (cursor.getInt(7) == 1), //is Media Type - cursor.getInt(8) //scannable + cursor.getInt(8) //scanScope ); } } \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/models/SyncedFileState.java b/app/src/main/java/foundation/e/drive/models/SyncedFileState.java deleted file mode 100644 index da09c0749acd58e5bad7a96de06a2be0e31a2dfd..0000000000000000000000000000000000000000 --- a/app/src/main/java/foundation/e/drive/models/SyncedFileState.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2022. - * 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 android.os.Parcel; -import android.os.Parcelable; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * @author Vincent Bourgmayer - * Describe a file state which will be Synchronized (= Synced) or which has already been synced one times - * - * 1. file state: no etag & LM <=0 : new local file discovered - * todo: shouldn't it be more case 2 below ? would break lot of codes - * 2. filestate: no etag but LM > 0 : ??? - * When does it happens ?, and what does it correspond to ? what side effect of such case ? - * 3. fileState: etag but LM <= 0 : new remote file discovered - * 4. fileState: etag & LM > 0: file synchronized once - */ -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; - - private int id; - private String name; //name of the file - private String localPath; //Path on the device file system - private String remotePath; //path on the server - private String lastETAG; //Last got Etag for the file - private long localLastModified; - private long syncedFolderId; - private boolean isMediaType; // true if this is a media synchronisable else it is a settings synchronisable. - private int scannable; - - /** - * Full constructor - * @param id database ID of SyncedFileState - * @param name name of file - * @param localPath Local path of file - * @param remotePath remote path of file - * @param etag last known etag of the file - * @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, @Nullable String name, @Nullable String localPath, @Nullable String remotePath, @Nullable String etag, long lastModified, long syncedFolderId, boolean isMediaType, int scannable){ - this.id = id; - this.name = name; - this.localPath = localPath; - this.remotePath = remotePath; - this.lastETAG = etag; - this.localLastModified = lastModified; - this.syncedFolderId = syncedFolderId; - this.isMediaType = isMediaType; - this.scannable = scannable; - } - - protected SyncedFileState(@NonNull Parcel in) { - id = in.readInt(); - name = in.readString(); - localPath = in.readString(); - remotePath = in.readString(); - lastETAG = in.readString(); - localLastModified = in.readLong(); - syncedFolderId = in.readLong(); - isMediaType = in.readByte() != 0; - scannable = in.readInt(); - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(id); - dest.writeString(name); - dest.writeString(localPath); - dest.writeString(remotePath); - dest.writeString(lastETAG); - dest.writeLong(localLastModified); - dest.writeLong(syncedFolderId); - dest.writeByte((byte) (isMediaType ? 1 : 0)); - dest.writeInt(scannable); - } - - public static final Creator CREATOR = new Creator() { - @Override - public SyncedFileState createFromParcel(Parcel in) { - return new SyncedFileState(in); - } - - @Override - public SyncedFileState[] newArray(int size) { - return new SyncedFileState[size]; - } - }; - - public int getId() { - return id; - } - - @NonNull - public SyncedFileState setId(int id) { - this.id = id; - return this; - } - - @Nullable - public String getName() { - return name; - } - - @Nullable - public String getLocalPath() { - return localPath; - } - - @Nullable - public String getRemotePath() { - return remotePath; - } - - @Nullable - public String getLastETAG() { - return lastETAG; - } - - @NonNull - public SyncedFileState setLastETAG(@Nullable String lastETAG) { - this.lastETAG = lastETAG; - return this; - } - - /** - * get lastModified value of local file - * @return Last Modified value of the file (long) - */ - public long getLocalLastModified() { - return localLastModified; - } - - @NonNull - public SyncedFileState setLocalLastModified(long localLastModified) { - this.localLastModified = localLastModified; - return this; - } - - /** - * Say if last Etag has been stored into instance - * @return True if lastEtag not null and not empty - */ - public boolean isLastEtagStored(){ - 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 - */ - public long getSyncedFolderId() { - return syncedFolderId; - } - - /** - * inform if its an media's or device/app's settings element - * @return boolean true if it's a media's element. - */ - public boolean isMediaType() { - 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; - } - - @Override - public String toString(){ - return "SyncedFileState :" - +"\nId: "+this.id - +"\nName: "+this.name - +"\nLocalPath: "+this.localPath - +"\nRemotePath: "+this.remotePath - +"\nLast Etag: "+this.lastETAG - +"\nLocal last modified: "+this.localLastModified - +"\nSyncedFolderId: "+this.syncedFolderId - +"\nisMediaType: "+this.isMediaType - +"\nscannable: "+this.scannable; - } - - @Override - public int describeContents() { - return 0; - } - - /** - * Disable syncing of the concerned file - */ - public void setUnsyncable() { - this.scannable = NOT_SCANNABLE; - } -} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/models/SyncedFileState.kt b/app/src/main/java/foundation/e/drive/models/SyncedFileState.kt new file mode 100644 index 0000000000000000000000000000000000000000..ccd10465557f0d1dcb0d5ccc99405096a6f6f63d --- /dev/null +++ b/app/src/main/java/foundation/e/drive/models/SyncedFileState.kt @@ -0,0 +1,113 @@ +/* + * Copyright © MURENA SAS 2022-2023. + * 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 android.os.Parcel +import android.os.Parcelable + + +const val DO_NOT_SCAN = 0 +const val SCAN_ON_CLOUD = 1 +const val SCAN_ON_DEVICE = 2 +const val SCAN_EVERYWHERE = 3 + +/** + * Class to hold data about a synchronized file + * + * 1. file state: no etag & mTime <=0 : new local file discovered + * 2. filestate: no etag but mTime > 0 : ??? + * When does it happens ?, and what does it correspond to ? what side effect of such case ? + * 3. fileState: etag but mTime <= 0 : new remote file discovered + * 4. fileState: etag & mTime > 0: file synchronized once + * + * @param id Id of syncedFileState in database + * @param name Name of the file + * @param localPath path to the file on the device + * @param remotePath path to the file on the cloud + * @param lastEtag Last known value of eTag, from cloud + * @param lastModified Last known value of "lastModified", from a file on device + * @param syncedFolderId Id of SyncedFolder (parent) from database + * @param scanScope define on which mount point the file should be scanned for change + * + * @author Vincent Bourgmayer + */ +data class SyncedFileState (var id: Int, + val name: String, + val localPath: String, + val remotePath: String, + var lastEtag: String, + var lastModified: Long, + val syncedFolderId: Long, + val isMediaType: Boolean, + var scanScope: Int) : Parcelable { + + constructor(parcel: Parcel) : this( + parcel.readInt(), + parcel.readString()!!, + parcel.readString()!!, + parcel.readString()!!, + parcel.readString()!!, + parcel.readLong(), + parcel.readLong(), + parcel.readByte() != 0.toByte(), + parcel.readInt() + ) { } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(id) + parcel.writeString(name) + parcel.writeString(localPath) + parcel.writeString(remotePath) + parcel.writeString(lastEtag) + parcel.writeLong(lastModified) + parcel.writeLong(syncedFolderId) + parcel.writeByte(if (isMediaType) 1 else 0) + parcel.writeInt(scanScope) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): SyncedFileState { + return SyncedFileState(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + + fun isLastEtagStored() : Boolean { + return this.lastEtag.isNotEmpty() + } + + fun hasBeenSynchronizedOnce(): Boolean { + return isLastEtagStored() && this.lastModified > 0L + } + + fun disableScanning() { + this.scanScope == DO_NOT_SCAN + } + + override fun toString(): String { + return """ + SyncedFileState : + Id: ${id} + Name: ${name} + LocalPath: ${localPath} + RemotePath: ${remotePath} + Last Etag: ${lastEtag} + Local last modified: ${lastModified} + SyncedFolderId: ${syncedFolderId} + isMediaType: ${isMediaType} + scannable: ${scanScope} + """.trimIndent() + } +} \ No newline at end of file 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 c54934544700e52a8677ff8b88f59a3c51a2d27e..256487fc3044897b131cc82dc7bbc8e67d7e09b8 100644 --- a/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java @@ -62,11 +62,12 @@ public class DownloadFileOperation extends RemoteOperation { Timber.tag(DownloadFileOperation.class.getSimpleName()); this.remoteFile = remoteFile; this.syncedFileState = syncedFileState; - this.previousEtag = syncedFileState.getLastETAG(); + this.previousEtag = syncedFileState.getLastEtag(); this.targetPath = syncedFileState.getLocalPath(); this.context = context; } + @SuppressWarnings("deprecation") @Override @NonNull protected RemoteOperationResult run(@NonNull OwnCloudClient ownCloudClient) { @@ -75,8 +76,8 @@ public class DownloadFileOperation extends RemoteOperation { this.syncedFileState.setId(DbHelper.manageSyncedFileStateDB(this.syncedFileState, "INSERT", context)); } - if (syncedFileState.getLastETAG().equals(remoteFile.getEtag()) - && syncedFileState.getLocalLastModified() > 0L) { + if (syncedFileState.getLastEtag().equals(remoteFile.getEtag()) + && syncedFileState.getLastModified() > 0L) { Timber.v( "%s already up-to-date", remoteFile.getRemotePath()); return new RemoteOperationResult(RemoteOperationResult.ResultCode.ETAG_UNCHANGED); } @@ -85,6 +86,7 @@ public class DownloadFileOperation extends RemoteOperation { final DownloadFileRemoteOperation downloadOperation = new DownloadFileRemoteOperation(remoteFile.getRemotePath(), tmpTargetFolderPath); + //noinspection deprecation final RemoteOperationResult downloadResult = downloadOperation.execute(ownCloudClient); RemoteOperationResult.ResultCode resultCode; @@ -107,7 +109,7 @@ public class DownloadFileOperation extends RemoteOperation { if (mustRestart) { Timber.v("%s unsuccessfull trial.s of downloading %s", restartCounter, remoteFile.getRemotePath()); - syncedFileState.setLastETAG(this.previousEtag); + syncedFileState.setLastEtag(this.previousEtag); if (this.restartCounter < 3) { this.restartCounter += 1; return this.run(ownCloudClient); @@ -153,8 +155,8 @@ public class DownloadFileOperation extends RemoteOperation { } targetFile.setLastModified(remoteFile.getModifiedTimestamp()); - syncedFileState.setLocalLastModified(targetFile.lastModified()) - .setLastETAG(remoteFile.getEtag()); + syncedFileState.setLastModified(targetFile.lastModified()); + syncedFileState.setLastEtag(remoteFile.getEtag()); doActionMediaScannerConnectionScanFile(context, syncedFileState.getLocalPath()); //required to make Gallery show new image return OK; @@ -185,7 +187,7 @@ public class DownloadFileOperation extends RemoteOperation { /** * Allow to read remote file path from the operation - * @return + * @return path of a remote file */ @Nullable public String getRemoteFilePath() { 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 141622bc8311c27cb58e045e61dd222570a87332..47cdd83e1870957d2b005cfa8132ae2d6f638132 100644 --- a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java @@ -1,6 +1,6 @@ /* * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2022. + * Copyright © MURENA SAS 2022-2023. * 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 @@ -20,6 +20,7 @@ import androidx.annotation.VisibleForTesting; import com.nextcloud.common.NextcloudClient; import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.Quota; import com.owncloud.android.lib.common.UserInfo; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; @@ -39,7 +40,6 @@ import java.util.Set; import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncedFileState; -import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; import timber.log.Timber; @@ -112,8 +112,8 @@ public class UploadFileOperation extends RemoteOperation { /** * Handle upload's failure - * @param uploadResult - * @param fileName + * @param uploadResult ResultCode for upload + * @param fileName name of the file * @return */ private ResultCode onUploadFailure(final ResultCode uploadResult, final String fileName) { @@ -153,8 +153,8 @@ public class UploadFileOperation extends RemoteOperation { //If file already up-to-date & synced if (syncedState.isLastEtagStored() - && syncedState.getLocalLastModified() == file.lastModified()) { - Timber.d("Synchronization conflict because: last modified from DB(%s) and from file (%s) are the same ", syncedState.getLocalLastModified(), file.lastModified()); + && syncedState.getLastModified() == file.lastModified()) { + Timber.d("Synchronization conflict because: last modified from DB(%s) and from file (%s) are the same ", syncedState.getLastModified(), file.lastModified()); return ResultCode.SYNC_CONFLICT; } @@ -198,9 +198,9 @@ public class UploadFileOperation extends RemoteOperation { final String etag = uploadResult.getResultData(); if (etag != null) { - syncedState.setLastETAG(etag); + syncedState.setLastEtag(etag); } - syncedState.setLocalLastModified(fileLastModified); + syncedState.setLastModified(fileLastModified); } /** @@ -208,12 +208,14 @@ public class UploadFileOperation extends RemoteOperation { * @param client OwnCloudClient * @return RemoteOperationResult */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + @VisibleForTesting() @NonNull public RemoteOperationResult checkAvailableSpace(@NonNull NextcloudClient client, long fileSize) { final RemoteOperationResult ocsResult = readUserInfo(client); final ResultCode resultCode; - if (ocsResult.isSuccess() && ocsResult.getResultData().getQuota().getFree() < fileSize) { + final Quota quotas = ocsResult.getResultData().getQuota(); + + if (ocsResult.isSuccess() && quotas != null && quotas.getFree() < fileSize) { resultCode = ResultCode.QUOTA_EXCEEDED; } else { resultCode = ocsResult.getCode(); @@ -228,7 +230,7 @@ public class UploadFileOperation extends RemoteOperation { * @param client client to run the method * @return RemoteOperationResult */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + @VisibleForTesting() @NonNull public RemoteOperationResult readUserInfo(@NonNull NextcloudClient client) { final GetUserInfoRemoteOperation GetUserInfoRemoteOperation = new GetUserInfoRemoteOperation(); @@ -241,10 +243,11 @@ public class UploadFileOperation extends RemoteOperation { * @param client Owncloudclient instance. @TODO will be replaced by NextcloudClient in future. * @return RemoteOperationResult instance containing failure details or RemoteFile instance */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + @VisibleForTesting() @NonNull public RemoteOperationResult readRemoteFile(@NonNull final String remotePath, @NonNull final OwnCloudClient client) { final ReadFileRemoteOperation readRemoteFile = new ReadFileRemoteOperation(remotePath); + //noinspection deprecation return readRemoteFile.execute(client); } @@ -255,15 +258,16 @@ public class UploadFileOperation extends RemoteOperation { * @param client OwncloudClient to perform the upload. @TODO will be replaced by NextcloudClient in future. * @return RemoteOperationResult instance containing success or failure status with details */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + @VisibleForTesting() @NonNull public RemoteOperationResult uploadChunkedFile(@NonNull final File file, @NonNull final OwnCloudClient client) { final String timeStamp = formatTimestampToMatchCloud(file.lastModified()); final String mimeType = getMimeType(file); final ChunkedFileUploadRemoteOperation uploadOperation = new ChunkedFileUploadRemoteOperation(syncedState.getLocalPath(), syncedState.getRemotePath(), - mimeType, syncedState.getLastETAG(), + mimeType, syncedState.getLastEtag(), timeStamp, false); + //noinspection deprecation return uploadOperation.execute(client); } @@ -273,9 +277,10 @@ public class UploadFileOperation extends RemoteOperation { * @param client OwncloudClient instance to perform the request * @return true if the remote file exist, false otherwise */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + @VisibleForTesting() public boolean remoteFileExist(@NonNull String remotePath, @NonNull OwnCloudClient client) { final ExistenceCheckRemoteOperation operation = new ExistenceCheckRemoteOperation(remotePath, false); + //noinspection deprecation final RemoteOperationResult result = operation.execute(client); return result.isSuccess(); } @@ -293,10 +298,10 @@ public class UploadFileOperation extends RemoteOperation { * @param client OwnCloudClient instance used to check if remote file exists * @return true if the if-match header should be added */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + @VisibleForTesting() public boolean ifMatchETagRequired(@NonNull OwnCloudClient client) { return syncedState.isMediaType() - && !syncedState.getLastETAG().isEmpty() + && syncedState.isLastEtagStored() && remoteFileExist(syncedState.getRemotePath(), client); } @@ -309,17 +314,18 @@ public class UploadFileOperation extends RemoteOperation { * @param checkEtag indicate if upload request must have a if-match header with eTag value * @return RemoteOperationResult the instance must contains etag in resultData if successful. */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + @VisibleForTesting() @NonNull public RemoteOperationResult uploadFile(@NonNull final File file, @NonNull final OwnCloudClient client, boolean checkEtag) { final String timeStamp = formatTimestampToMatchCloud(file.lastModified()); - final String eTag = checkEtag ? syncedState.getLastETAG() : null; + final String eTag = checkEtag ? syncedState.getLastEtag() : null; final UploadFileRemoteOperation uploadOperation = new UploadFileRemoteOperation(syncedState.getLocalPath(), syncedState.getRemotePath(), getMimeType(file), - eTag, //If not null, This can cause error 412; that means remote file has change + eTag, //If not null, it can cause error HTTP 412; which means remote file has change timeStamp); + //noinspection deprecation return uploadOperation.execute(client); } @@ -329,10 +335,11 @@ public class UploadFileOperation extends RemoteOperation { * @param client Client to perform the request. TODO will be replaced by NextcloudClient in future. * @return true if the parent directory has been created, false either */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + @VisibleForTesting() public boolean createRemoteFolder(@NonNull String targetPath, @NonNull OwnCloudClient client) { final String remoteFolderPath = targetPath.substring(0, targetPath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1); final CreateFolderRemoteOperation createFolderOperation = new CreateFolderRemoteOperation(remoteFolderPath, true); + //noinspection deprecation final RemoteOperationResult createFolderResult = createFolderOperation.execute(client); return createFolderResult.isSuccess() || createFolderResult.getCode() == ResultCode.FOLDER_ALREADY_EXISTS; } @@ -347,7 +354,7 @@ public class UploadFileOperation extends RemoteOperation { * On Android, file timestamp is a ms value, while nextcloud expect value in second * @return String timestamp in second */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + @VisibleForTesting() public @NonNull String formatTimestampToMatchCloud(long timestamp) { return String.valueOf(timestamp/1000); } 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 a9de67c0bc17d81f26f8c109036865ac15dcb020..7f5fe817b9a71706715bb152836247a733f7b003 100644 --- a/app/src/main/java/foundation/e/drive/utils/FileDiffUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/FileDiffUtils.java @@ -1,5 +1,5 @@ /* - * Copyright © ECORP SAS 2022. + * Copyright © MURENA SAS 2022-2023. * 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 @@ -59,7 +59,7 @@ public class FileDiffUtils { @NonNull public static Action getActionForFileDiff(@NonNull File localFile, @NonNull 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()) { + if (fileState.getLastModified() < localFile.lastModified() || !fileState.isLastEtagStored()) { return Action.Upload; } return Action.skip; @@ -73,7 +73,7 @@ public class FileDiffUtils { */ private static boolean hasEtagChanged(@NonNull RemoteFile file, @NonNull SyncedFileState fileState) { //if SyncedFileState has no Etag then it hasn't been uploaded and so must not exist on server - return fileState.isLastEtagStored() && !file.getEtag().equals(fileState.getLastETAG()); + return fileState.isLastEtagStored() && !file.getEtag().equals(fileState.getLastEtag()); } /** @@ -83,7 +83,7 @@ public class FileDiffUtils { * @return true if localLastModified store in Database == 0 */ private static boolean hasAlreadyBeenDownloaded(@NonNull SyncedFileState fileState) { - return fileState.getLocalLastModified() > 0L; + return fileState.getLastModified() > 0L; } /** diff --git a/app/src/main/java/foundation/e/drive/work/FileSyncDisabler.java b/app/src/main/java/foundation/e/drive/work/FileSyncDisabler.java index 7b883737d100884f6c05b03334c77a728a6e2f73..76af0fd90d58106bcc8b281eacb9a2489986eae2 100644 --- a/app/src/main/java/foundation/e/drive/work/FileSyncDisabler.java +++ b/app/src/main/java/foundation/e/drive/work/FileSyncDisabler.java @@ -40,7 +40,7 @@ public class FileSyncDisabler { public Runnable getRunnable(@NonNull final Handler handler, final int threadId, @NonNull final Context context, @NonNull final FileSyncDisablingListener listener) { return () -> { - fileState.setUnsyncable(); + fileState.disableScanning(); if (DbHelper.manageSyncedFileStateDB(fileState, "UPDATE", context) <= 0) { Timber.d("Failed to remove %s from DB", fileState.getName()); } diff --git a/app/src/test/java/foundation/e/drive/contentScanner/LocalContentScannerTest.java b/app/src/test/java/foundation/e/drive/contentScanner/LocalContentScannerTest.java index b686ee06a3e1496c1efed2c76d5d4b9c16971172..e57efbf6383017b08e88d4e122eff293a0abab59 100644 --- a/app/src/test/java/foundation/e/drive/contentScanner/LocalContentScannerTest.java +++ b/app/src/test/java/foundation/e/drive/contentScanner/LocalContentScannerTest.java @@ -1,5 +1,5 @@ /* - * Copyright © MURENA SAS 2022. + * Copyright © MURENA SAS 2022-2023. * 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 @@ -9,8 +9,7 @@ package foundation.e.drive.contentScanner; import static org.mockito.Mockito.when; - -import static foundation.e.drive.models.SyncedFileState.ALL_SCANNABLE; +import static foundation.e.drive.models.SyncedFileStateKt.SCAN_EVERYWHERE; import android.content.Context; import android.os.Build; @@ -65,8 +64,8 @@ public class LocalContentScannerTest { final List localFiles = new ArrayList<>(); final List syncedFileStates = new ArrayList<>(); - final SyncedFileState syncedFileState1 = new SyncedFileState(1, "titi.png", "local/path/titi.png", "remote/path/titi.png", "aaaa", 123456, 0, true, ALL_SCANNABLE); - final SyncedFileState syncedFileState2 = new SyncedFileState(2, "tutu.png", "local/path/tutu.png", "remote/path/tutu.png", "bbbb", 234567, 0, true, ALL_SCANNABLE); + final SyncedFileState syncedFileState1 = new SyncedFileState(1, "titi.png", "local/path/titi.png", "remote/path/titi.png", "aaaa", 123456, 0, true, SCAN_EVERYWHERE); + final SyncedFileState syncedFileState2 = new SyncedFileState(2, "tutu.png", "local/path/tutu.png", "remote/path/tutu.png", "bbbb", 234567, 0, true, SCAN_EVERYWHERE); syncedFileStates.add(syncedFileState1); syncedFileStates.add(syncedFileState2); @@ -88,8 +87,8 @@ public class LocalContentScannerTest { localFiles.add(file2); final List syncedFileStates = new ArrayList<>(); - final SyncedFileState syncedFileState1 = new SyncedFileState(1, file1.getName(), file1.getAbsolutePath(), "remote/path/titi.png", "", 0, 0, true, ALL_SCANNABLE); - final SyncedFileState syncedFileState2 = new SyncedFileState(2, file2.getName(), file2.getAbsolutePath(), "remote/path/tutu.png", "", 0, 0, true, ALL_SCANNABLE); + final SyncedFileState syncedFileState1 = new SyncedFileState(1, file1.getName(), file1.getAbsolutePath(), "remote/path/titi.png", "", 0, 0, true, SCAN_EVERYWHERE); + final SyncedFileState syncedFileState2 = new SyncedFileState(2, file2.getName(), file2.getAbsolutePath(), "remote/path/tutu.png", "", 0, 0, true, SCAN_EVERYWHERE); syncedFileStates.add(syncedFileState1); syncedFileStates.add(syncedFileState2); @@ -112,8 +111,8 @@ public class LocalContentScannerTest { localFiles.add(file2); final List syncedFileStates = new ArrayList<>(); - final SyncedFileState syncedFileState1 = new SyncedFileState(1, file1.getName(), file1.getAbsolutePath(), "remote/path/titi.png", "aaaa", 123456, 0, true, ALL_SCANNABLE); - final SyncedFileState syncedFileState2 = new SyncedFileState(2, file2.getName(), file2.getAbsolutePath(), "remote/path/tutu.png", "bbbb", 234567, 0, true, ALL_SCANNABLE); + final SyncedFileState syncedFileState1 = new SyncedFileState(1, file1.getName(), file1.getAbsolutePath(), "remote/path/titi.png", "aaaa", 123456, 0, true, SCAN_EVERYWHERE); + final SyncedFileState syncedFileState2 = new SyncedFileState(2, file2.getName(), file2.getAbsolutePath(), "remote/path/tutu.png", "bbbb", 234567, 0, true, SCAN_EVERYWHERE); syncedFileStates.add(syncedFileState1); syncedFileStates.add(syncedFileState2); diff --git a/app/src/test/java/foundation/e/drive/contentScanner/LocalFileListerTest.java b/app/src/test/java/foundation/e/drive/contentScanner/LocalFileListerTest.java index 83c7d3248d0d3bb0c1662c650ad4611bbbe4856e..40e41d2ce12206ca56a59ea6ef965613e1c93482 100644 --- a/app/src/test/java/foundation/e/drive/contentScanner/LocalFileListerTest.java +++ b/app/src/test/java/foundation/e/drive/contentScanner/LocalFileListerTest.java @@ -1,5 +1,5 @@ /* - * Copyright © ECORP SAS 2022. + * Copyright © MURENA SAS 2022-2023. * 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,7 +8,8 @@ package foundation.e.drive.contentScanner; -import static foundation.e.drive.models.SyncedFileState.ALL_SCANNABLE; + +import static foundation.e.drive.models.SyncedFileStateKt.SCAN_EVERYWHERE; import android.content.Context; import android.database.sqlite.SQLiteDatabase; @@ -147,7 +148,7 @@ public class LocalFileListerTest { final File directory = Mockito.spy(testRootDirectory); Mockito.when(directory.lastModified()).thenReturn(lastModified); - final SyncedFileState dummy = new SyncedFileState(6, "foo.jpg", dirPath + "foo.jpg", "/remote/path/test/foo.jpg", "", 0L, 12, true, ALL_SCANNABLE); + final SyncedFileState dummy = new SyncedFileState(6, "foo.jpg", dirPath + "foo.jpg", "/remote/path/test/foo.jpg", "", 0L, 12, true, SCAN_EVERYWHERE); DbHelper.manageSyncedFileStateDB(dummy, "INSERT", context); final SyncedFolder syncedFolder = new SyncedFolder("any", dirPath , "/remote/path/test/", true, true, true, true); @@ -184,7 +185,7 @@ public class LocalFileListerTest { syncedFolder.setId(12); syncedFolder.setLastModified(123456L); - final SyncedFileState dummy = new SyncedFileState(6, "foo.jpg", dirPath + "foo.jpg", "/remote/path/test/foo.jpg", "", 0L, 12, true, ALL_SCANNABLE); + final SyncedFileState dummy = new SyncedFileState(6, "foo.jpg", dirPath + "foo.jpg", "/remote/path/test/foo.jpg", "", 0L, 12, true, SCAN_EVERYWHERE); DbHelper.manageSyncedFileStateDB(dummy, "INSERT", context); final boolean skip = fileListerUnderTest.skipDirectory(directory, syncedFolder, context); diff --git a/app/src/test/java/foundation/e/drive/contentScanner/RemoteContentScannerTest.java b/app/src/test/java/foundation/e/drive/contentScanner/RemoteContentScannerTest.java index 192cf38d7ea29b7f362cdb66905d528c1c386715..476d96166759c33677d5260be9506ebaae749674 100644 --- a/app/src/test/java/foundation/e/drive/contentScanner/RemoteContentScannerTest.java +++ b/app/src/test/java/foundation/e/drive/contentScanner/RemoteContentScannerTest.java @@ -1,5 +1,5 @@ /* - * Copyright © MURENA SAS 2022. + * Copyright © MURENA SAS 2022-2023. * 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 @@ -9,7 +9,7 @@ package foundation.e.drive.contentScanner; import static org.junit.Assert.assertEquals; -import static foundation.e.drive.models.SyncedFileState.ALL_SCANNABLE; +import static foundation.e.drive.models.SyncedFileStateKt.SCAN_EVERYWHERE; import android.content.Context; import android.os.Build; @@ -84,14 +84,11 @@ public class RemoteContentScannerTest { @Test public void scanContent_withoutRemoteContent_returnListWithLocalDeleteRequest() { final List cloudContent = new ArrayList<>(); - Assert.assertTrue("Fake list of cloud Content is not empty", cloudContent.isEmpty()); final List dbContent = new ArrayList<>(); - dbContent.add(new SyncedFileState(5, "toto", "local/path/toto", "remote/path/toto", "5555", 22222, 2, true, ALL_SCANNABLE)); - dbContent.add(new SyncedFileState(3, "titi", "local/path/titi", "remote/path/titi", "5556", 22322, 2, true, ALL_SCANNABLE)); - dbContent.add(new SyncedFileState(2, "tata", "local/path/tata", "remote/path/tata", "5557", 22232, 2, true, ALL_SCANNABLE)); - - Assert.assertFalse("List of SyncedFileState is empty", dbContent.isEmpty()); + dbContent.add(new SyncedFileState(5, "toto", "local/path/toto", "remote/path/toto", "5555", 22222, 2, true, SCAN_EVERYWHERE)); + dbContent.add(new SyncedFileState(3, "titi", "local/path/titi", "remote/path/titi", "5556", 22322, 2, true, SCAN_EVERYWHERE)); + dbContent.add(new SyncedFileState(2, "tata", "local/path/tata", "remote/path/tata", "5557", 22232, 2, true, SCAN_EVERYWHERE)); final HashMap scanResult = scannerUnderTest.scanContent(cloudContent, dbContent); @@ -128,18 +125,18 @@ public class RemoteContentScannerTest { final RemoteFile updatedFile1 = Mockito.mock(RemoteFile.class); Mockito.when(updatedFile1.getRemotePath()).thenReturn("remote/path/toto"); Mockito.when(updatedFile1.getEtag()).thenReturn("33423"); - Mockito.when(updatedFile1.getSize()).thenReturn(12l); + Mockito.when(updatedFile1.getSize()).thenReturn(12L); final RemoteFile updatedFile2 = Mockito.mock(RemoteFile.class); Mockito.when(updatedFile2.getRemotePath()).thenReturn("remote/path/titi"); Mockito.when(updatedFile2.getEtag()).thenReturn("34523"); - Mockito.when(updatedFile2.getSize()).thenReturn(14l); + Mockito.when(updatedFile2.getSize()).thenReturn(14L); - final RemoteFile uptodateFile1 = Mockito.mock(RemoteFile.class); - Mockito.when(uptodateFile1.getRemotePath()).thenReturn("remote/path/tata"); - Mockito.when(uptodateFile1.getEtag()).thenReturn("5557"); + final RemoteFile upTtoDateFile = Mockito.mock(RemoteFile.class); + Mockito.when(upTtoDateFile.getRemotePath()).thenReturn("remote/path/tata"); + Mockito.when(upTtoDateFile.getEtag()).thenReturn("5557"); - cloudContent.add(uptodateFile1); + cloudContent.add(upTtoDateFile); cloudContent.add(updatedFile1); cloudContent.add(updatedFile2); @@ -147,11 +144,9 @@ public class RemoteContentScannerTest { final List dbContent = new ArrayList<>(); - dbContent.add(new SyncedFileState(5, "toto", "local/path/toto", "remote/path/toto", "5555", 22222, 2, true, ALL_SCANNABLE)); - dbContent.add(new SyncedFileState(3, "titi", "local/path/titi", "remote/path/titi", "5556", 22322, 2, true, ALL_SCANNABLE)); - dbContent.add(new SyncedFileState(2, "tata", "local/path/tata", "remote/path/tata", "5557", 22232, 2, true, ALL_SCANNABLE)); - - Assert.assertFalse("List of SyncedFileState is empty", dbContent.isEmpty()); + dbContent.add(new SyncedFileState(5, "toto", "local/path/toto", "remote/path/toto", "5555", 22222, 2, true, SCAN_EVERYWHERE)); + dbContent.add(new SyncedFileState(3, "titi", "local/path/titi", "remote/path/titi", "5556", 22322, 2, true, SCAN_EVERYWHERE)); + dbContent.add(new SyncedFileState(2, "tata", "local/path/tata", "remote/path/tata", "5557", 22232, 2, true, SCAN_EVERYWHERE)); final HashMap scanResult = scannerUnderTest.scanContent(cloudContent, dbContent); Assert.assertEquals("scanResult's size doesn't match the expected result", 2, scanResult.size()); @@ -173,11 +168,11 @@ public class RemoteContentScannerTest { Mockito.when(updatedFile2.getRemotePath()).thenReturn("remote/path/titi"); Mockito.when(updatedFile2.getEtag()).thenReturn("5556"); - final RemoteFile uptodateFile1 = Mockito.mock(RemoteFile.class); - Mockito.when(uptodateFile1.getRemotePath()).thenReturn("remote/path/tata"); - Mockito.when(uptodateFile1.getEtag()).thenReturn("5557"); + final RemoteFile upToDateFile = Mockito.mock(RemoteFile.class); + Mockito.when(upToDateFile.getRemotePath()).thenReturn("remote/path/tata"); + Mockito.when(upToDateFile.getEtag()).thenReturn("5557"); - cloudContent.add(uptodateFile1); + cloudContent.add(upToDateFile); cloudContent.add(updatedFile1); cloudContent.add(updatedFile2); @@ -185,11 +180,9 @@ public class RemoteContentScannerTest { final List dbContent = new ArrayList<>(); - dbContent.add(new SyncedFileState(5, "toto", "local/path/toto", "remote/path/toto", "5555", 22222, 2, true, ALL_SCANNABLE)); - dbContent.add(new SyncedFileState(3, "titi", "local/path/titi", "remote/path/titi", "5556", 22322, 2, true, ALL_SCANNABLE)); - dbContent.add(new SyncedFileState(2, "tata", "local/path/tata", "remote/path/tata", "5557", 22232, 2, true, ALL_SCANNABLE)); - - Assert.assertFalse("List of SyncedFileState is empty", dbContent.isEmpty()); + dbContent.add(new SyncedFileState(5, "toto", "local/path/toto", "remote/path/toto", "5555", 22222, 2, true, SCAN_EVERYWHERE)); + dbContent.add(new SyncedFileState(3, "titi", "local/path/titi", "remote/path/titi", "5556", 22322, 2, true, SCAN_EVERYWHERE)); + dbContent.add(new SyncedFileState(2, "tata", "local/path/tata", "remote/path/tata", "5557", 22232, 2, true, SCAN_EVERYWHERE)); final HashMap scanResult = scannerUnderTest.scanContent(cloudContent, dbContent); Assert.assertEquals("scanResult's size doesn't match the expected result", 2, scanResult.size()); diff --git a/app/src/test/java/foundation/e/drive/contentScanner/RemoteFileListerTest.java b/app/src/test/java/foundation/e/drive/contentScanner/RemoteFileListerTest.java index 235cddf52e7d9363fe3d75e6a60843301ac7bc56..c421c8a72006aa8b044dc8eeb63c0605e3558198 100644 --- a/app/src/test/java/foundation/e/drive/contentScanner/RemoteFileListerTest.java +++ b/app/src/test/java/foundation/e/drive/contentScanner/RemoteFileListerTest.java @@ -1,5 +1,5 @@ /* - * Copyright © ECORP SAS 2022. + * Copyright © MURENA SAS 2022-2023. * 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 @@ -10,7 +10,7 @@ package foundation.e.drive.contentScanner; import static org.mockito.Mockito.when; -import static foundation.e.drive.models.SyncedFileState.ALL_SCANNABLE; +import static foundation.e.drive.models.SyncedFileStateKt.SCAN_EVERYWHERE; import android.accounts.Account; import android.accounts.AccountManager; @@ -217,7 +217,7 @@ public class RemoteFileListerTest { syncedFolder.setId(12); syncedFolder.setLastEtag("5555aaaa"); - final SyncedFileState dummy = new SyncedFileState(6, "foo.jpg", "local/foo.jpg", TEST_FILE_TREE_ROOT_PATH + "foo.jpg", "7777", 0L, 12, true, ALL_SCANNABLE); + final SyncedFileState dummy = new SyncedFileState(6, "foo.jpg", "local/foo.jpg", TEST_FILE_TREE_ROOT_PATH + "foo.jpg", "7777", 0L, 12, true, SCAN_EVERYWHERE); DbHelper.manageSyncedFileStateDB(dummy, "INSERT", context); final boolean skip = fileListerUnderTest.skipDirectory(remoteFile, syncedFolder, context); @@ -233,7 +233,7 @@ public class RemoteFileListerTest { syncedFolder.setId(12); syncedFolder.setLastEtag("5555aaaa"); - final SyncedFileState dummy = new SyncedFileState(6, "foo.jpg", "local/foo.jpg", TEST_FILE_TREE_ROOT_PATH + "foo.jpg", "7777", 0L, 12, true, ALL_SCANNABLE); + final SyncedFileState dummy = new SyncedFileState(6, "foo.jpg", "local/foo.jpg", TEST_FILE_TREE_ROOT_PATH + "foo.jpg", "7777", 0L, 12, true, SCAN_EVERYWHERE); DbHelper.manageSyncedFileStateDB(dummy, "INSERT", context); final boolean skip = fileListerUnderTest.skipDirectory(remoteFile, syncedFolder, context); diff --git a/app/src/test/java/foundation/e/drive/database/SyncedFileStateDAOTest.java b/app/src/test/java/foundation/e/drive/database/SyncedFileStateDAOTest.java index cd301b120ed18ce2323198dbdd5eb5f6021cbd88..561a3f5dbcbd5581faa3e871facc2d67e492cd40 100644 --- a/app/src/test/java/foundation/e/drive/database/SyncedFileStateDAOTest.java +++ b/app/src/test/java/foundation/e/drive/database/SyncedFileStateDAOTest.java @@ -1,5 +1,5 @@ /* - * Copyright © MURENA SAS 2022. + * Copyright © MURENA SAS 2022-2023. * 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 @@ -7,8 +7,7 @@ */ package foundation.e.drive.database; - -import static foundation.e.drive.models.SyncedFileState.ALL_SCANNABLE; +import static foundation.e.drive.models.SyncedFileStateKt.SCAN_EVERYWHERE; import android.content.Context; import android.os.Build; @@ -18,7 +17,6 @@ import androidx.test.core.app.ApplicationProvider; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -51,7 +49,7 @@ public class SyncedFileStateDAOTest { dummyFolder.setId(12); DbHelper.insertSyncedFolder(dummyFolder, context); - final SyncedFileState dummy = new SyncedFileState(6, "foo.jpg", "local/foo.jpg", "remote/foo.jpg", "7777", 0L, 12, true, ALL_SCANNABLE); + final SyncedFileState dummy = new SyncedFileState(6, "foo.jpg", "local/foo.jpg", "remote/foo.jpg", "7777", 0L, 12, true, SCAN_EVERYWHERE); DbHelper.manageSyncedFileStateDB(dummy, "INSERT", context); daoUnderTest = DbHelper.openSyncedFileStateDAO(context, false); 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 f95e2eae4d07b5743e36f6f2c711070b12b4d9fd..e57b66038dd59342bf6c82b0b58e0d9f8d7dcd0a 100644 --- a/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java +++ b/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java @@ -7,7 +7,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static foundation.e.drive.models.SyncedFileState.ALL_SCANNABLE; + +import static foundation.e.drive.models.SyncedFileStateKt.SCAN_EVERYWHERE; import android.accounts.Account; import android.accounts.AccountManager; @@ -46,17 +47,16 @@ import foundation.e.drive.utils.DavClientProvider; @RunWith(RobolectricTestRunner.class) @Config(sdk = Build.VERSION_CODES.O, manifest = Config.NONE) public class UploadFileOperationTest { - private List syncedFileStates= new ArrayList<>(); + private final List syncedFileStates= new ArrayList<>(); private final OwnCloudClient ocClient; private final NextcloudClient ncClient; - private final AccountManager accountManager; private final Context context; private final long userFreeQuota = 50; private final Account account; public UploadFileOperationTest() { context = ApplicationProvider.getApplicationContext(); - accountManager = AccountManager.get(context); + final AccountManager accountManager = AccountManager.get(context); ShadowLog.stream = System.out; TestUtils.loadServerCredentials(); TestUtils.prepareValidAccount(accountManager); @@ -111,7 +111,7 @@ public class UploadFileOperationTest { "dummy.txt", TestUtils.TEST_LOCAL_ROOT_FOLDER_PATH + "small/dummy.txt", TestUtils.TEST_REMOTE_ROOT_FOLDER_PATH + "small/dummy.txt", - "", 0L, 0, true, ALL_SCANNABLE); + "", 0L, 0, true, SCAN_EVERYWHERE); sfs.setId(DbHelper.manageSyncedFileStateDB(sfs, "INSERT", context)); syncedFileStates.add(sfs); @@ -145,17 +145,18 @@ public class UploadFileOperationTest { @Ignore("Fail on gitlab, reason not found yet") @Test public void uploadNewSmallFile_shouldwork() { - removeSmallFile(); //clean the environnement + removeSmallFile(); //clean the environment final File file = createSmallFile(); //preparation final SyncedFileState sfs_fromDB = DbHelper.loadSyncedFile(context, syncedFileStates.get(0).getLocalPath(), true); - assertTrue("SyncedFileState loaded from DB must have an empty ETag", sfs_fromDB.getLastETAG().isEmpty()); + assertTrue("SyncedFileState loaded from DB must have an empty ETag", sfs_fromDB.getLastEtag().isEmpty()); final UploadFileOperation operation = Mockito.spy(new UploadFileOperation(syncedFileStates.get(0), account, context)); mockCreateRemoteDir(true, operation, sfs_fromDB.getRemotePath()); mockUserInfoReading(operation); mockFileHeadOperation(true, operation, sfs_fromDB.getRemotePath()); mockSuccessfulFileUpload(operation, file, false); + //noinspection deprecation final RemoteOperationResult result = operation.execute(ocClient); String errorMsg = "The upload failed:\n http code: " + result.getHttpCode() + "\n, is success ?" + result.isSuccess() @@ -167,7 +168,7 @@ public class UploadFileOperationTest { assertTrue( errorMsg, result.isSuccess()); final SyncedFileState sfs_fromDBAfterUpload = DbHelper.loadSyncedFile(context, syncedFileStates.get(0).getLocalPath(), true); - assertFalse("After upload, the database must store the etag of the syncedFileState. But here it is empty", sfs_fromDBAfterUpload.getLastETAG().isEmpty()); + assertFalse("After upload, the database must store the etag of the syncedFileState. But here it is empty", sfs_fromDBAfterUpload.getLastEtag().isEmpty()); } /** @@ -176,18 +177,18 @@ public class UploadFileOperationTest { */ @Test public void localFileRemovedBeforeUpload_shouldFail() { - removeSmallFile(); //clean the environnement + removeSmallFile(); //clean the environment createSmallFile(); //preparation final SyncedFileState sfs_fromDB = DbHelper.loadSyncedFile(context, syncedFileStates.get(0).getLocalPath(), true); - assertTrue("SyncedFileState loaded from DB must have an empty Etag", sfs_fromDB.getLastETAG().isEmpty()); + assertTrue("SyncedFileState loaded from DB must have an empty Etag", sfs_fromDB.getLastEtag().isEmpty()); - boolean checkEtag = false; - UploadFileOperation testOperation = new UploadFileOperation(syncedFileStates.get(0), account, context); + final UploadFileOperation testOperation = new UploadFileOperation(syncedFileStates.get(0), account, context); final File smallFile = new File(sfs_fromDB.getLocalPath()); assertTrue("Local file deletion return false instead of true", smallFile.delete()); + //noinspection deprecation final RemoteOperationResult result = testOperation.execute(ocClient); assertEquals("Expected result code was FORBIDDEN but got: " + result.getCode().name(), RemoteOperationResult.ResultCode.FORBIDDEN, result.getCode()); } @@ -250,7 +251,7 @@ public class UploadFileOperationTest { 11111220, 1, false, - ALL_SCANNABLE); + SCAN_EVERYWHERE); assertFalse("SyncedFileState should not have a media type but it has", testSyncedState.isMediaType()); @@ -269,9 +270,9 @@ public class UploadFileOperationTest { 11111220, 1, true, - ALL_SCANNABLE); + SCAN_EVERYWHERE); - assertTrue("SyncedFileState's ETag should be empty but isn't", testSyncedState.getLastETAG().isEmpty()); + assertTrue("SyncedFileState's ETag should be empty but isn't", testSyncedState.getLastEtag().isEmpty()); assertTrue("SyncedFileState is not a media type but it should", testSyncedState.isMediaType()); final UploadFileOperation operationUnderTest = new UploadFileOperation(testSyncedState, account, context); final boolean addIfMatchHeader =operationUnderTest.ifMatchETagRequired(ocClient); @@ -288,9 +289,9 @@ public class UploadFileOperationTest { 11111220, 1, true, - ALL_SCANNABLE); + SCAN_EVERYWHERE); - assertFalse("SyncedFileState's ETag should not be empty but isn't", testSyncedState.getLastETAG().isEmpty()); + assertFalse("SyncedFileState's ETag should not be empty but isn't", testSyncedState.getLastEtag().isEmpty()); assertTrue("SyncedFileState is not a media type but it should", testSyncedState.isMediaType()); final UploadFileOperation operationUnderTest = Mockito.spy(new UploadFileOperation(testSyncedState, account, context)); mockFileHeadOperation(false, operationUnderTest, testSyncedState.getRemotePath()); @@ -308,9 +309,9 @@ public class UploadFileOperationTest { 11111220, 1, true, - ALL_SCANNABLE); + SCAN_EVERYWHERE); - assertFalse("SyncedFileState's ETag should not be empty but isn't", testSyncedState.getLastETAG().isEmpty()); + assertFalse("SyncedFileState's ETag should not be empty but isn't", testSyncedState.getLastEtag().isEmpty()); assertTrue("SyncedFileState is not a media type but it should", testSyncedState.isMediaType()); final UploadFileOperation operationUnderTest = Mockito.spy(new UploadFileOperation(testSyncedState, account, context)); mockFileHeadOperation(true, operationUnderTest, testSyncedState.getRemotePath());