Skip to content

Commit

Permalink
Handle offline operations better
Browse files Browse the repository at this point in the history
Signed-off-by: alperozturk <alper_ozturk@proton.me>
  • Loading branch information
alperozturk96 committed Aug 20, 2024
1 parent c7fbd63 commit 54c3d54
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@ import androidx.work.WorkerParameters
import com.nextcloud.client.account.User
import com.nextcloud.client.database.entity.OfflineOperationEntity
import com.nextcloud.client.network.ClientFactoryImpl
import com.nextcloud.client.network.ConnectivityService
import com.nextcloud.model.OfflineOperationType
import com.nextcloud.model.WorkerState
import com.nextcloud.model.WorkerStateLiveData
import com.nextcloud.receiver.NetworkChangeReceiver
import com.nextcloud.utils.extensions.showToast
import com.owncloud.android.R
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
Expand All @@ -28,11 +26,11 @@ import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.withContext

class OfflineOperationsWorker(
private val user: User,
private val context: Context,
private val connectivityService: ConnectivityService,
viewThemeUtils: ViewThemeUtils,
params: WorkerParameters
) : CoroutineWorker(context, params) {
Expand All @@ -46,7 +44,7 @@ class OfflineOperationsWorker(
private val clientFactory = ClientFactoryImpl(context)
private val notificationManager = OfflineOperationsNotificationManager(context, viewThemeUtils)

@Suppress("Deprecation")
@Suppress("TooGenericExceptionCaught", "Deprecation")
override suspend fun doWork(): Result = coroutineScope {
val jobName = inputData.getString(JOB_NAME)
Log_OC.d(
Expand All @@ -56,7 +54,7 @@ class OfflineOperationsWorker(
"\n-----------------------------------"
)

if (!NetworkChangeReceiver.isNetworkAvailable(context)) {
if (!connectivityService.isNetworkAndServerAvailable()) {
Log_OC.d(TAG, "OfflineOperationsWorker cancelled, no internet connection")
return@coroutineScope Result.success()
}
Expand All @@ -73,17 +71,32 @@ class OfflineOperationsWorker(
notificationManager.start()

offlineOperations.forEachIndexed { index, operation ->
when (operation.type) {
OfflineOperationType.CreateFolder -> {
val createFolderOperation = async(Dispatchers.IO) { createFolder(operation) }.await()
val result = createFolderOperation?.execute(client)
handleResult(operation, offlineOperations.size, index, result, createFolderOperation)
}

else -> {
Log_OC.d(TAG, "Operation terminated, not supported operation type")
val result = try {
when (operation.type) {
OfflineOperationType.CreateFolder -> {
val createFolderOperation = async(Dispatchers.IO) {
CreateFolderOperation(
operation.path,
user,
context,
fileDataStorageManager
)
}.await()

createFolderOperation.execute(client) to createFolderOperation
}

else -> {
Log_OC.d(TAG, "Operation terminated, not supported operation type")
null
}
}
} catch (e: Exception) {
Log_OC.d(TAG, "Operation terminated, exception caught: $e")
null
}

handleResult(operation, offlineOperations.size, index, result?.first, result?.second)
}

Log_OC.d(TAG, "OfflineOperationsWorker successfully completed")
Expand All @@ -99,40 +112,19 @@ class OfflineOperationsWorker(
result: RemoteOperationResult<*>?,
remoteOperation: RemoteOperation<*>?
) {
if (result == null) {
Log_OC.d(TAG, "Operation not completed, result is null")
return
}
result ?: return Log_OC.d(TAG, "Operation not completed, result is null")

val logMessage = if (result.isSuccess) "Operation completed" else "Operation terminated"
Log_OC.d(TAG, "$logMessage path: ${operation.path}, type: ${operation.type}")

if (result.isSuccess) {
fileDataStorageManager.offlineOperationDao.delete(operation)
notificationManager.update(operationSize, currentOperationIndex, operation.filename ?: "")
} else if (result.code == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS) {
context.showToast(context.getString(R.string.folder_already_exists_server, operation.filename))
// fileDataStorageManager.offlineOperationDao.delete(operation)
}
} else {
val excludedErrorCodes = listOf(RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS)

val logMessage = if (result.isSuccess) "Operation completed" else "Operation terminated"
val operationLog = "$logMessage path: ${operation.path}, type: ${operation.type}"
Log_OC.d(TAG, operationLog)

if (!result.isSuccess && remoteOperation != null) {
notificationManager.showNewNotification(result, remoteOperation)
}
}

@Suppress("TooGenericExceptionCaught")
private suspend fun createFolder(
operation: OfflineOperationEntity,
): RemoteOperation<*>? {
return withContext(Dispatchers.IO) {
val createFolderOperation = CreateFolderOperation(operation.path, user, context, fileDataStorageManager)

try {
createFolderOperation
} catch (e: Exception) {
Log_OC.d(TAG, "Create folder operation terminated, $e")
null
if (remoteOperation != null && !excludedErrorCodes.contains(result.code)) {
notificationManager.showNewNotification(result, remoteOperation)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,35 @@

package com.nextcloud.utils.extensions

import com.nextcloud.client.database.entity.OfflineOperationEntity
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.files.model.RemoteFile
import com.owncloud.android.utils.FileStorageUtils

fun RemoteOperationResult<*>?.getConflictedRemoteIdsWithOfflineOperations(
offlineOperations: List<OfflineOperationEntity>
): Pair<ArrayList<String>, ArrayList<String?>>? {
val newFiles = toOCFile() ?: return null

val (remoteIds, offlineOperationsPaths) = newFiles
.flatMap { file ->
offlineOperations
.filter { it.filename == file.fileName }
.map { file.remoteId to it.path }
}
.unzip()

return ArrayList(remoteIds) to ArrayList(offlineOperationsPaths)
}

@Suppress("Deprecation")
fun RemoteOperationResult<*>?.toOCFile(): List<OCFile>? {
return this?.data?.toOCFileList()
return if (this?.isSuccess == true) {
data?.toOCFileList()
} else {
null
}
}

private fun ArrayList<Any>.toOCFileList(): List<OCFile> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@

import android.content.Context;
import android.content.Intent;
import android.util.Pair;

import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.nextcloud.android.lib.resources.directediting.DirectEditingObtainRemoteOperation;
import com.nextcloud.client.account.User;
import com.nextcloud.client.database.entity.OfflineOperationEntity;
import com.nextcloud.common.NextcloudClient;
import com.nextcloud.utils.extensions.RemoteOperationResultExtensionsKt;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
Expand Down Expand Up @@ -53,7 +51,6 @@
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.theme.CapabilityUtils;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -227,26 +224,6 @@ public List<OCFile> getChildren() {
return mChildren;
}

private Pair<ArrayList<String>, ArrayList<String>> getConflictedRemoteIdsWithOfflineOperations(RemoteOperationResult operationResult) {
List<OfflineOperationEntity> offlineOperations = mStorageManager.offlineOperationDao.getAll();
List<OCFile> newFiles = RemoteOperationResultExtensionsKt.toOCFile(operationResult);
if (newFiles == null) return null;

ArrayList<String> conflictedOfflineOperationsPaths = new ArrayList<>();
ArrayList<String> newFilesRemoteIds = new ArrayList<>();

for (OCFile file: newFiles) {
for (OfflineOperationEntity offlineOperation: offlineOperations) {
if (file.getFileName().equals(offlineOperation.getFilename())) {
newFilesRemoteIds.add(file.getRemoteId());
conflictedOfflineOperationsPaths.add(offlineOperation.getPath());
}
}
}

return new Pair<>(newFilesRemoteIds, conflictedOfflineOperationsPaths);
}

/**
* Performs the synchronization.
* <p>
Expand Down Expand Up @@ -285,9 +262,10 @@ protected RemoteOperationResult run(OwnCloudClient client) {
mStorageManager.saveFile(mLocalFolder);
}

Pair<ArrayList<String>, ArrayList<String>> conflictedRemoteIdsAndOfflineOperationPaths = getConflictedRemoteIdsWithOfflineOperations(result);
if (conflictedRemoteIdsAndOfflineOperationPaths != null && !conflictedRemoteIdsAndOfflineOperationPaths.first.isEmpty() && !conflictedRemoteIdsAndOfflineOperationPaths.second.isEmpty()) {
sendFolderSyncConflictEventBroadcast(conflictedRemoteIdsAndOfflineOperationPaths);
var offlineOperations = mStorageManager.offlineOperationDao.getAll();
var conflictData = RemoteOperationResultExtensionsKt.getConflictedRemoteIdsWithOfflineOperations(result, offlineOperations);
if (conflictData != null && !conflictData.getFirst().isEmpty() && !conflictData.getSecond().isEmpty()) {
sendFolderSyncConflictEventBroadcast(conflictData.getFirst(), conflictData.getSecond());
}

if (!mSyncFullAccount && mRemoteFolderChanged) {
Expand All @@ -309,10 +287,10 @@ protected RemoteOperationResult run(OwnCloudClient client) {
return result;
}

private void sendFolderSyncConflictEventBroadcast(Pair<ArrayList<String>, ArrayList<String>> conflictedRemoteIdsAndOfflineOperationPaths) {
private void sendFolderSyncConflictEventBroadcast(ArrayList<String> newFiles, ArrayList<String> offlineOperationPaths) {
Intent intent = new Intent(FileDisplayActivity.FOLDER_SYNC_CONFLICT);
intent.putStringArrayListExtra(FileDisplayActivity.FOLDER_SYNC_CONFLICT_NEW_FILES, conflictedRemoteIdsAndOfflineOperationPaths.first);
intent.putStringArrayListExtra(FileDisplayActivity.FOLDER_SYNC_CONFLICT_OFFLINE_OPERATION_PATHS, conflictedRemoteIdsAndOfflineOperationPaths.second);
intent.putStringArrayListExtra(FileDisplayActivity.FOLDER_SYNC_CONFLICT_NEW_FILES, newFiles);
intent.putStringArrayListExtra(FileDisplayActivity.FOLDER_SYNC_CONFLICT_OFFLINE_OPERATION_PATHS, offlineOperationPaths);
LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.common.collect.Sets
import com.nextcloud.client.di.Injectable
import com.nextcloud.receiver.NetworkChangeReceiver
import com.nextcloud.client.network.ConnectivityService
import com.nextcloud.utils.extensions.getParcelableArgument
import com.owncloud.android.R
import com.owncloud.android.databinding.EditBoxDialogBinding
Expand All @@ -37,6 +38,8 @@ import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.KeyboardUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject

/**
Expand All @@ -55,6 +58,9 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList
@Inject
lateinit var keyboardUtils: KeyboardUtils

@Inject
lateinit var connectivityService: ConnectivityService

private var mParentFolder: OCFile? = null
private var positiveButton: MaterialButton? = null

Expand Down Expand Up @@ -174,14 +180,19 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList
}

val path = mParentFolder?.decryptedRemotePath + newFolderName + OCFile.PATH_SEPARATOR
if (NetworkChangeReceiver.isNetworkAvailable(requireContext())) {
(requireActivity() as ComponentsGetter).fileOperationsHelper.createFolder(path)
} else {
Log_OC.d(TAG, "Network not available, creating offline operation")
fileDataStorageManager.addCreateFolderOfflineOperation(path, newFolderName, mParentFolder?.fileId)

val fileDisplayActivity = requireActivity() as? FileDisplayActivity
fileDisplayActivity?.syncAndUpdateFolder(true)

lifecycleScope.launch(Dispatchers.IO) {
if (connectivityService.isNetworkAndServerAvailable()) {
(requireActivity() as ComponentsGetter).fileOperationsHelper.createFolder(path)
} else {
Log_OC.d(TAG, "Network not available, creating offline operation")
fileDataStorageManager.addCreateFolderOfflineOperation(path, newFolderName, mParentFolder?.fileId)

launch(Dispatchers.Main) {
val fileDisplayActivity = requireActivity() as? FileDisplayActivity
fileDisplayActivity?.syncAndUpdateFolder(true)
}
}
}
}
}
Expand Down
1 change: 0 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
<string name="menu_item_sort_by_size_biggest_first">Biggest first</string>
<string name="menu_item_sort_by_size_smallest_first">Smallest first</string>
<string name="oc_file_list_adapter_offline_operation_description_text">Pending Operation</string>
<string name="folder_already_exists_server">%s folder already exists on the server, no changes were made</string>
<string name="ecosystem_apps_display_assistant">Assistant</string>
<string name="unexpected_error_occurred">Unexpected error occurred</string>
<string name="folder_not_created_yet">Folder not created yet</string>
Expand Down

0 comments on commit 54c3d54

Please sign in to comment.