|
@@ -58,15 +58,14 @@ import java.util.Vector;
|
|
|
import androidx.annotation.NonNull;
|
|
|
import androidx.annotation.Nullable;
|
|
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
|
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
|
|
|
|
|
import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
|
|
|
|
|
|
/**
|
|
|
- * Remote operation performing the synchronization of the list of files contained in a folder identified with its
|
|
|
- * remote path.
|
|
|
- * Fetches the list and properties of the files contained in the given folder, including their properties, and updates
|
|
|
- * the local database with them.
|
|
|
- * Does NOT enter in the child folders to synchronize their contents also.
|
|
|
+ * Remote operation performing the synchronization of the list of files contained in a folder identified with its remote
|
|
|
+ * path. Fetches the list and properties of the files contained in the given folder, including their properties, and
|
|
|
+ * updates the local database with them. Does NOT enter in the child folders to synchronize their contents also.
|
|
|
*/
|
|
|
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
|
|
|
public class RefreshFolderOperation extends RemoteOperation {
|
|
@@ -74,37 +73,53 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
private static final String TAG = RefreshFolderOperation.class.getSimpleName();
|
|
|
|
|
|
public static final String EVENT_SINGLE_FOLDER_CONTENTS_SYNCED =
|
|
|
- RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_CONTENTS_SYNCED";
|
|
|
+ RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_CONTENTS_SYNCED";
|
|
|
public static final String EVENT_SINGLE_FOLDER_SHARES_SYNCED =
|
|
|
- RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_SHARES_SYNCED";
|
|
|
+ RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_SHARES_SYNCED";
|
|
|
|
|
|
- /** Time stamp for the synchronization process in progress */
|
|
|
+ /**
|
|
|
+ * Time stamp for the synchronization process in progress
|
|
|
+ */
|
|
|
private long mCurrentSyncTime;
|
|
|
|
|
|
- /** Remote folder to synchronize */
|
|
|
+ /**
|
|
|
+ * Remote folder to synchronize
|
|
|
+ */
|
|
|
private OCFile mLocalFolder;
|
|
|
|
|
|
- /** Access to the local database */
|
|
|
+ /**
|
|
|
+ * Access to the local database
|
|
|
+ */
|
|
|
private FileDataStorageManager mStorageManager;
|
|
|
|
|
|
- /** Account where the file to synchronize belongs */
|
|
|
+ /**
|
|
|
+ * Account where the file to synchronize belongs
|
|
|
+ */
|
|
|
private User user;
|
|
|
|
|
|
- /** Android context; necessary to send requests to the download service */
|
|
|
+ /**
|
|
|
+ * Android context; necessary to send requests to the download service
|
|
|
+ */
|
|
|
private Context mContext;
|
|
|
|
|
|
- /** Files and folders contained in the synchronized folder after a successful operation */
|
|
|
+ /**
|
|
|
+ * Files and folders contained in the synchronized folder after a successful operation
|
|
|
+ */
|
|
|
private List<OCFile> mChildren;
|
|
|
|
|
|
- /** Counter of conflicts found between local and remote files */
|
|
|
+ /**
|
|
|
+ * Counter of conflicts found between local and remote files
|
|
|
+ */
|
|
|
private int mConflictsFound;
|
|
|
|
|
|
- /** Counter of failed operations in synchronization of kept-in-sync files */
|
|
|
+ /**
|
|
|
+ * Counter of failed operations in synchronization of kept-in-sync files
|
|
|
+ */
|
|
|
private int mFailsInKeptInSyncFound;
|
|
|
|
|
|
/**
|
|
|
- * Map of remote and local paths to files that where locally stored in a location
|
|
|
- * out of the ownCloud folder and couldn't be copied automatically into it
|
|
|
+ * Map of remote and local paths to files that where locally stored in a location out of the ownCloud folder and
|
|
|
+ * couldn't be copied automatically into it
|
|
|
**/
|
|
|
private Map<String, String> mForgottenLocalFiles;
|
|
|
|
|
@@ -113,10 +128,14 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
*/
|
|
|
private boolean mSyncFullAccount;
|
|
|
|
|
|
- /** 'True' means that the remote folder changed and should be fetched */
|
|
|
+ /**
|
|
|
+ * 'True' means that the remote folder changed and should be fetched
|
|
|
+ */
|
|
|
private boolean mRemoteFolderChanged;
|
|
|
|
|
|
- /** 'True' means that Etag will be ignored */
|
|
|
+ /**
|
|
|
+ * 'True' means that Etag will be ignored
|
|
|
+ */
|
|
|
private boolean mIgnoreETag;
|
|
|
|
|
|
/**
|
|
@@ -131,16 +150,14 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
/**
|
|
|
* Creates a new instance of {@link RefreshFolderOperation}.
|
|
|
*
|
|
|
- * @param folder Folder to synchronize.
|
|
|
- * @param currentSyncTime Time stamp for the synchronization process in progress.
|
|
|
- * @param syncFullAccount 'True' means that this operation is part of a full account
|
|
|
- * synchronization.
|
|
|
- * @param ignoreETag 'True' means that the content of the remote folder should
|
|
|
- * be fetched and updated even though the 'eTag' did not
|
|
|
- * change.
|
|
|
- * @param dataStorageManager Interface with the local database.
|
|
|
- * @param user ownCloud account where the folder is located.
|
|
|
- * @param context Application context.
|
|
|
+ * @param folder Folder to synchronize.
|
|
|
+ * @param currentSyncTime Time stamp for the synchronization process in progress.
|
|
|
+ * @param syncFullAccount 'True' means that this operation is part of a full account synchronization.
|
|
|
+ * @param ignoreETag 'True' means that the content of the remote folder should be fetched and updated even
|
|
|
+ * though the 'eTag' did not change.
|
|
|
+ * @param dataStorageManager Interface with the local database.
|
|
|
+ * @param user ownCloud account where the folder is located.
|
|
|
+ * @param context Application context.
|
|
|
*/
|
|
|
public RefreshFolderOperation(OCFile folder,
|
|
|
long currentSyncTime,
|
|
@@ -196,8 +213,8 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Returns the list of files and folders contained in the synchronized folder,
|
|
|
- * if called after synchronization is complete.
|
|
|
+ * Returns the list of files and folders contained in the synchronized folder, if called after synchronization is
|
|
|
+ * complete.
|
|
|
*
|
|
|
* @return List of files and folders contained in the synchronized folder.
|
|
|
*/
|
|
@@ -207,7 +224,7 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
|
|
|
/**
|
|
|
* Performs the synchronization.
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* {@inheritDoc}
|
|
|
*/
|
|
|
@Override
|
|
@@ -246,7 +263,7 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
if (!mSyncFullAccount && mRemoteFolderChanged) {
|
|
|
sendLocalBroadcast(
|
|
|
EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result
|
|
|
- );
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
if (result.isSuccess() && !mSyncFullAccount && !mOnlyFileMetadata) {
|
|
@@ -256,7 +273,7 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
if (!mSyncFullAccount) {
|
|
|
sendLocalBroadcast(
|
|
|
EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result
|
|
|
- );
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
return result;
|
|
@@ -371,7 +388,7 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
result = new RemoteOperationResult(ResultCode.OK);
|
|
|
|
|
|
Log_OC.i(TAG, "Checked " + user.getAccountName() + remotePath + " : " +
|
|
|
- (mRemoteFolderChanged ? "changed" : "not changed"));
|
|
|
+ (mRemoteFolderChanged ? "changed" : "not changed"));
|
|
|
|
|
|
} else {
|
|
|
// check failed
|
|
@@ -380,10 +397,10 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
}
|
|
|
if (result.isException()) {
|
|
|
Log_OC.e(TAG, "Checked " + user.getAccountName() + remotePath + " : " +
|
|
|
- result.getLogMessage(), result.getException());
|
|
|
+ result.getLogMessage(), result.getException());
|
|
|
} else {
|
|
|
Log_OC.e(TAG, "Checked " + user.getAccountName() + remotePath + " : " +
|
|
|
- result.getLogMessage());
|
|
|
+ result.getLogMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -425,9 +442,9 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
|
|
|
|
|
|
/**
|
|
|
- * Synchronizes the data retrieved from the server about the contents of the target folder
|
|
|
- * with the current data in the local database.
|
|
|
- *
|
|
|
+ * Synchronizes the data retrieved from the server about the contents of the target folder with the current data in
|
|
|
+ * the local database.
|
|
|
+ * <p>
|
|
|
* Grants that mChildren is updated with fresh data after execution.
|
|
|
*
|
|
|
* @param folderAndFiles Remote folder and children files in Folder
|
|
@@ -553,6 +570,7 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
updatedFiles.add(updatedFile);
|
|
|
}
|
|
|
|
|
|
+
|
|
|
// save updated contents in local database
|
|
|
// update file name for encrypted files
|
|
|
if (e2EVersion == E2EVersion.V1_2) {
|
|
@@ -584,6 +602,37 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
return metadata;
|
|
|
}
|
|
|
|
|
|
+ @SuppressFBWarnings("CE")
|
|
|
+ private static void setMimeTypeAndDecryptedRemotePath(OCFile updatedFile, FileDataStorageManager storageManager, String decryptedFileName, String mimetype) {
|
|
|
+ OCFile parentFile = storageManager.getFileById(updatedFile.getParentId());
|
|
|
+
|
|
|
+ if (parentFile == null) {
|
|
|
+ throw new NullPointerException("parentFile cannot be null");
|
|
|
+ }
|
|
|
+
|
|
|
+ String decryptedRemotePath;
|
|
|
+ if (decryptedFileName != null) {
|
|
|
+ decryptedRemotePath = parentFile.getDecryptedRemotePath() + decryptedFileName;
|
|
|
+ } else {
|
|
|
+ decryptedRemotePath = parentFile.getRemotePath() + updatedFile.getFileName();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (updatedFile.isFolder()) {
|
|
|
+ decryptedRemotePath += "/";
|
|
|
+ }
|
|
|
+ updatedFile.setDecryptedRemotePath(decryptedRemotePath);
|
|
|
+
|
|
|
+ if (mimetype == null || mimetype.isEmpty()) {
|
|
|
+ if (updatedFile.isFolder()) {
|
|
|
+ updatedFile.setMimeType(MimeType.DIRECTORY);
|
|
|
+ } else {
|
|
|
+ updatedFile.setMimeType("application/octet-stream");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ updatedFile.setMimeType(mimetype);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public static void updateFileNameForEncryptedFileV1(FileDataStorageManager storageManager,
|
|
|
@NonNull DecryptedFolderMetadataFileV1 metadata,
|
|
|
OCFile updatedFile) {
|
|
@@ -597,28 +646,16 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
} else {
|
|
|
com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile decryptedFile =
|
|
|
metadata.getFiles().get(updatedFile.getFileName());
|
|
|
- decryptedFileName = decryptedFile.getEncrypted().getFilename();
|
|
|
- mimetype = decryptedFile.getEncrypted().getMimetype();
|
|
|
- }
|
|
|
-
|
|
|
|
|
|
- OCFile parentFile = storageManager.getFileById(updatedFile.getParentId());
|
|
|
- String decryptedRemotePath = parentFile.getDecryptedRemotePath() + decryptedFileName;
|
|
|
+ if (decryptedFile == null) {
|
|
|
+ throw new NullPointerException("decryptedFile cannot be null");
|
|
|
+ }
|
|
|
|
|
|
- if (updatedFile.isFolder()) {
|
|
|
- decryptedRemotePath += "/";
|
|
|
+ decryptedFileName = decryptedFile.getEncrypted().getFilename();
|
|
|
+ mimetype = decryptedFile.getEncrypted().getMimetype();
|
|
|
}
|
|
|
- updatedFile.setDecryptedRemotePath(decryptedRemotePath);
|
|
|
|
|
|
- if (mimetype == null || mimetype.isEmpty()) {
|
|
|
- if (updatedFile.isFolder()) {
|
|
|
- updatedFile.setMimeType(MimeType.DIRECTORY);
|
|
|
- } else {
|
|
|
- updatedFile.setMimeType("application/octet-stream");
|
|
|
- }
|
|
|
- } else {
|
|
|
- updatedFile.setMimeType(mimetype);
|
|
|
- }
|
|
|
+ setMimeTypeAndDecryptedRemotePath(updatedFile, storageManager, decryptedFileName, mimetype);
|
|
|
} catch (NullPointerException e) {
|
|
|
Log_OC.e(TAG, "DecryptedMetadata for file " + updatedFile.getFileId() + " not found!");
|
|
|
}
|
|
@@ -636,28 +673,16 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
mimetype = MimeType.DIRECTORY;
|
|
|
} else {
|
|
|
DecryptedFile decryptedFile = metadata.getMetadata().getFiles().get(updatedFile.getFileName());
|
|
|
- decryptedFileName = decryptedFile.getFilename();
|
|
|
- mimetype = decryptedFile.getMimetype();
|
|
|
- }
|
|
|
-
|
|
|
|
|
|
- OCFile parentFile = storageManager.getFileById(updatedFile.getParentId());
|
|
|
- String decryptedRemotePath = parentFile.getDecryptedRemotePath() + decryptedFileName;
|
|
|
+ if (decryptedFile == null) {
|
|
|
+ throw new NullPointerException("decryptedFile cannot be null");
|
|
|
+ }
|
|
|
|
|
|
- if (updatedFile.isFolder()) {
|
|
|
- decryptedRemotePath += "/";
|
|
|
+ decryptedFileName = decryptedFile.getFilename();
|
|
|
+ mimetype = decryptedFile.getMimetype();
|
|
|
}
|
|
|
- updatedFile.setDecryptedRemotePath(decryptedRemotePath);
|
|
|
|
|
|
- if (mimetype == null || mimetype.isEmpty()) {
|
|
|
- if (updatedFile.isFolder()) {
|
|
|
- updatedFile.setMimeType(MimeType.DIRECTORY);
|
|
|
- } else {
|
|
|
- updatedFile.setMimeType("application/octet-stream");
|
|
|
- }
|
|
|
- } else {
|
|
|
- updatedFile.setMimeType(mimetype);
|
|
|
- }
|
|
|
+ setMimeTypeAndDecryptedRemotePath(updatedFile, storageManager, decryptedFileName, mimetype);
|
|
|
} catch (NullPointerException e) {
|
|
|
Log_OC.e(TAG, "DecryptedMetadata for file " + updatedFile.getFileId() + " not found!");
|
|
|
}
|
|
@@ -668,8 +693,8 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
updatedFile.setFileId(localFile.getFileId());
|
|
|
updatedFile.setLastSyncDateForData(localFile.getLastSyncDateForData());
|
|
|
updatedFile.setModificationTimestampAtLastSyncForData(
|
|
|
- localFile.getModificationTimestampAtLastSyncForData()
|
|
|
- );
|
|
|
+ localFile.getModificationTimestampAtLastSyncForData()
|
|
|
+ );
|
|
|
if (localFile.isEncrypted()) {
|
|
|
if (mLocalFolder.getStoragePath() == null) {
|
|
|
updatedFile.setStoragePath(FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), mLocalFolder) +
|
|
@@ -685,7 +710,7 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
|
|
|
// eTag will not be updated unless file CONTENTS are synchronized
|
|
|
if (!updatedFile.isFolder() && localFile.isDown() &&
|
|
|
- !updatedFile.getEtag().equals(localFile.getEtag())) {
|
|
|
+ !updatedFile.getEtag().equals(localFile.getEtag())) {
|
|
|
updatedFile.setEtagInConflict(updatedFile.getEtag());
|
|
|
}
|
|
|
|
|
@@ -695,8 +720,8 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
updatedFile.setFileLength(remoteFile.getFileLength());
|
|
|
updatedFile.setMountType(remoteFile.getMountType());
|
|
|
} else if (remoteFolderChanged && MimeTypeUtil.isImage(remoteFile) &&
|
|
|
- remoteFile.getModificationTimestamp() !=
|
|
|
- localFile.getModificationTimestamp()) {
|
|
|
+ remoteFile.getModificationTimestamp() !=
|
|
|
+ localFile.getModificationTimestamp()) {
|
|
|
updatedFile.setUpdateThumbnailNeeded(true);
|
|
|
Log_OC.d(TAG, "Image " + remoteFile.getFileName() + " updated on the server");
|
|
|
}
|
|
@@ -713,6 +738,7 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
}
|
|
|
|
|
|
@NonNull
|
|
|
+ @SuppressFBWarnings("OCP")
|
|
|
public static Map<String, OCFile> prefillLocalFilesMap(Object metadata, List<OCFile> localFiles) {
|
|
|
Map<String, OCFile> localFilesMap = Maps.newHashMapWithExpectedSize(localFiles.size());
|
|
|
|
|
@@ -731,13 +757,12 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Performs a list of synchronization operations, determining if a download or upload is needed
|
|
|
- * or if exists conflict due to changes both in local and remote contents of the each file.
|
|
|
- *
|
|
|
- * If download or upload is needed, request the operation to the corresponding service and goes
|
|
|
- * on.
|
|
|
+ * Performs a list of synchronization operations, determining if a download or upload is needed or if exists
|
|
|
+ * conflict due to changes both in local and remote contents of the each file.
|
|
|
+ * <p>
|
|
|
+ * If download or upload is needed, request the operation to the corresponding service and goes on.
|
|
|
*
|
|
|
- * @param filesToSyncContents Synchronization operations to execute.
|
|
|
+ * @param filesToSyncContents Synchronization operations to execute.
|
|
|
*/
|
|
|
private void startContentSynchronizations(List<SynchronizeFileOperation> filesToSyncContents) {
|
|
|
RemoteOperationResult contentsResult;
|
|
@@ -750,10 +775,10 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
mFailsInKeptInSyncFound++;
|
|
|
if (contentsResult.getException() != null) {
|
|
|
Log_OC.e(TAG, "Error while synchronizing favourites : "
|
|
|
- + contentsResult.getLogMessage(), contentsResult.getException());
|
|
|
+ + contentsResult.getLogMessage(), contentsResult.getException());
|
|
|
} else {
|
|
|
Log_OC.e(TAG, "Error while synchronizing favourites : "
|
|
|
- + contentsResult.getLogMessage());
|
|
|
+ + contentsResult.getLogMessage());
|
|
|
}
|
|
|
}
|
|
|
} // won't let these fails break the synchronization process
|
|
@@ -763,9 +788,9 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
/**
|
|
|
* Syncs the Share resources for the files contained in the folder refreshed (children, not deeper descendants).
|
|
|
*
|
|
|
- * @param client Handler of a session with an OC server.
|
|
|
- * @return The result of the remote operation retrieving the Share resources in the folder refreshed by
|
|
|
- * the operation.
|
|
|
+ * @param client Handler of a session with an OC server.
|
|
|
+ * @return The result of the remote operation retrieving the Share resources in the folder refreshed by the
|
|
|
+ * operation.
|
|
|
*/
|
|
|
private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) {
|
|
|
RemoteOperationResult result;
|
|
@@ -793,12 +818,10 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Sends a message to any application component interested in the progress
|
|
|
- * of the synchronization.
|
|
|
+ * Sends a message to any application component interested in the progress of the synchronization.
|
|
|
*
|
|
|
* @param event broadcast event (Intent Action)
|
|
|
- * @param dirRemotePath Remote path of a folder that was just synchronized
|
|
|
- * (with or without success)
|
|
|
+ * @param dirRemotePath Remote path of a folder that was just synchronized (with or without success)
|
|
|
* @param result remote operation result
|
|
|
*/
|
|
|
private void sendLocalBroadcast(String event, String dirRemotePath, RemoteOperationResult result) {
|