Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/androidTest/java/com/owncloud/android/FileIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public void testRenameFolder() throws IOException {
folderPath,
user,
fileDataStorageManager,
false,
false)
.execute(targetContext)
.isSuccess());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,15 @@ class InternalTwoWaySyncWork(
}

Log_OC.d(TAG, "Folder ${folder.remotePath}: started!")
operation = SynchronizeFolderOperation(context, folder.remotePath, user, fileDataStorageManager, true)
operation =
SynchronizeFolderOperation(
context,
folder.remotePath,
user,
fileDataStorageManager,
false,
false
)
val operationResult = operation?.execute(context)

if (operationResult?.isSuccess == true) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,15 @@ class OfflineSyncWork(
val files = folder.listFiles { obj: File -> obj.isFile }
if (files != null) {
for (file in files) {
val ocFile = storageManager.getFileByLocalPath(file.path)
val ocFile = storageManager.getFileByLocalPath(file.path) ?: continue

val synchronizeFileOperation = SynchronizeFileOperation(
ocFile?.remotePath,
ocFile.remotePath,
user,
true,
context,
storageManager,
true,
false,
false
)
synchronizeFileOperation.execute(context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.operations.DownloadFileOperation
import com.owncloud.android.operations.DownloadType
import com.owncloud.android.ui.helpers.FileOperationsHelper
import com.owncloud.android.utils.FileStorageUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.util.concurrent.ConcurrentHashMap

@Suppress("LongMethod", "TooGenericExceptionCaught")
/**
* Only used for downloading files in first level of the folder, not subfolders.
*/
@Suppress("LongMethod", "TooGenericExceptionCaught", "MagicNumber")
class FolderDownloadWorker(
private val accountManager: UserAccountManager,
private val context: Context,
Expand All @@ -39,13 +43,11 @@ class FolderDownloadWorker(
private const val TAG = "📂" + "FolderDownloadWorker"
const val FOLDER_ID = "FOLDER_ID"
const val ACCOUNT_NAME = "ACCOUNT_NAME"

private val pendingDownloads: MutableSet<Long> = ConcurrentHashMap.newKeySet<Long>()

private val pendingDownloads: MutableSet<Long> = ConcurrentHashMap.newKeySet()
fun isDownloading(id: Long): Boolean = pendingDownloads.contains(id)
}

private val notificationManager = FolderDownloadWorkerNotificationManager(context, viewThemeUtils)
private val notificationManager = FolderDownloadWorkerNotificationManager(context, true, viewThemeUtils)
private val folderDownloadEventBroadcaster = FolderDownloadEventBroadcaster(context, localBroadcastManager)
private lateinit var storageManager: FileDataStorageManager

Expand All @@ -58,88 +60,99 @@ class FolderDownloadWorker(

val accountName = inputData.getString(ACCOUNT_NAME)
if (accountName == null) {
Log_OC.e(TAG, "failed accountName cannot be null")
Log_OC.e(TAG, "failed: accountName cannot be null")
return Result.failure()
}

val optionalUser = accountManager.getUser(accountName)
if (optionalUser.isEmpty) {
Log_OC.e(TAG, "failed user is not present")
Log_OC.e(TAG, "failed: user is not present")
return Result.failure()
}

val user = optionalUser.get()
storageManager = FileDataStorageManager(user, context.contentResolver)

val folder = storageManager.getFileById(folderID)
if (folder == null) {
Log_OC.e(TAG, "failed folder cannot be nul")
Log_OC.e(TAG, "failed: folder cannot be null")
return Result.failure()
}

Log_OC.d(TAG, "🕒 started for ${user.accountName} downloading ${folder.fileName}")
Log_OC.d(TAG, "🕒 started for ${user.accountName} | folder=${folder.fileName}")

trySetForeground(folder)

folderDownloadEventBroadcaster.sendDownloadEnqueued(folder.fileId)
pendingDownloads.add(folder.fileId)

val downloadHelper = FileDownloadHelper.instance()

return withContext(Dispatchers.IO) {
try {
val files = getFiles(folder, storageManager)
val account = user.toOwnCloudAccount()
val client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(account, context)
val client = OwnCloudClientManagerFactory.getDefaultSingleton()
.getClientFor(account, context)
val files = getFiles(folder, storageManager)
if (files.isEmpty()) {
Log_OC.d(TAG, "✅ no files need downloading")
notificationManager.showCompletionNotification(folder.fileName, true)
return@withContext Result.success()
}

var overallSuccess = true

var result = true
files.forEachIndexed { index, file ->
if (!checkDiskSize(file)) {
if (isStopped) {
Log_OC.d(TAG, "⚠️ worker stopped mid-download, aborting remaining files")
return@withContext Result.failure()
}

withContext(Dispatchers.Main) {
val notification = notificationManager.getProgressNotification(
folder.fileName,
file.fileName,
index,
files.size
)
notificationManager.showNotification(notification)

val foregroundInfo = notificationManager.getForegroundInfo(notification)
setForeground(foregroundInfo)
if (!FileStorageUtils.checkIfEnoughSpace(folder)) {
notificationManager.showNotAvailableDiskSpace()
return@withContext Result.failure()
}

notificationManager.showProgressNotification(
folder.fileName,
file.fileName,
index,
files.size
)

val operation = DownloadFileOperation(user, file, context)
val operationResult = operation.execute(client)

if (operationResult?.isSuccess == true && operation.downloadType === DownloadType.DOWNLOAD) {
getOCFile(operation)?.let { ocFile ->
downloadHelper.saveFile(ocFile, operation, storageManager)
}
}

if (!operationResult.isSuccess) {
result = false
if (operationResult?.isSuccess != true) {
Log_OC.w(TAG, "⚠️ download failed for ${file.remotePath}: ${operationResult?.logMessage}")
overallSuccess = false
}
}

withContext(Dispatchers.Main) {
notificationManager.showCompletionNotification(folder.fileName, result)
}
notificationManager.showCompletionNotification(folder.fileName, overallSuccess)

if (result) {
Log_OC.d(TAG, "✅ completed")
if (overallSuccess) {
Log_OC.d(TAG, "✅ completed successfully")
Result.success()
} else {
Log_OC.d(TAG, "❌ failed")
Log_OC.d(TAG, "❌ completed with failures")
Result.failure()
}
} catch (e: Exception) {
Log_OC.d(TAG, "❌ failed reason: $e")
Log_OC.e(TAG, "❌ unexpected failure: $e")
notificationManager.showCompletionNotification(folder.fileName, false)
Result.failure()
} finally {
folderDownloadEventBroadcaster.sendDownloadCompleted(folder.fileId)
pendingDownloads.remove(folder.fileId)

// delay so that user can see the error or success notification
delay(2000)
notificationManager.dismiss()
}
}
Expand All @@ -155,11 +168,12 @@ class FolderDownloadWorker(
return notificationManager.getForegroundInfo(null)
}

val folder = storageManager.getFileById(folderID) ?: return notificationManager.getForegroundInfo(null)
val folder = storageManager.getFileById(folderID)
?: return notificationManager.getForegroundInfo(null)

return notificationManager.getForegroundInfo(folder)
notificationManager.getForegroundInfo(folder)
} catch (e: Exception) {
Log_OC.w(TAG, "⚠️ Error getting foreground info: ${e.message}")
Log_OC.w(TAG, "⚠️ error getting foreground info: ${e.message}")
notificationManager.getForegroundInfo(null)
}
}
Expand All @@ -169,34 +183,20 @@ class FolderDownloadWorker(
val foregroundInfo = notificationManager.getForegroundInfo(folder)
setForeground(foregroundInfo)
} catch (e: Exception) {
Log_OC.w(TAG, "⚠️ Could not set foreground service: ${e.message}")
Log_OC.w(TAG, "⚠️ could not set foreground service: ${e.message}")
}
}

private fun getOCFile(operation: DownloadFileOperation): OCFile? {
val file = operation.file?.fileId?.let { storageManager.getFileById(it) }
?: storageManager.getFileByDecryptedRemotePath(operation.file?.remotePath)
?: run {
Log_OC.e(TAG, "could not save ${operation.file?.remotePath}")
return null
}

return file
private fun getOCFile(operation: DownloadFileOperation): OCFile? = operation.file?.fileId?.let {
storageManager.getFileById(it)
}
?: storageManager.getFileByDecryptedRemotePath(operation.file?.remotePath)
?: run {
Log_OC.e(TAG, "could not resolve OCFile for save: ${operation.file?.remotePath}")
null
}

private fun getFiles(folder: OCFile, storageManager: FileDataStorageManager): List<OCFile> =
storageManager.getFolderContent(folder, false)
.filter { !it.isFolder && !it.isDown }

private fun checkDiskSize(file: OCFile): Boolean {
val fileSizeInByte = file.fileLength
val availableDiskSpace = FileOperationsHelper.getAvailableSpaceOnDevice()

return if (availableDiskSpace < fileSizeInByte) {
notificationManager.showNotAvailableDiskSpace()
false
} else {
true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,26 @@ import android.content.Context
import android.content.Intent
import androidx.work.ForegroundInfo
import com.nextcloud.client.jobs.notification.WorkerNotificationManager
import com.nextcloud.utils.extensions.mainThread
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.ui.notifications.NotificationUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.random.Random

class FolderDownloadWorkerNotificationManager(private val context: Context, viewThemeUtils: ViewThemeUtils) :
WorkerNotificationManager(
id = NOTIFICATION_ID,
context,
viewThemeUtils,
tickerId = R.string.folder_download_worker_ticker_id,
channelId = NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD
) {
class FolderDownloadWorkerNotificationManager(
private val context: Context,
private val showCancel: Boolean = true,
viewThemeUtils: ViewThemeUtils?
) : WorkerNotificationManager(
id = NOTIFICATION_ID,
context,
viewThemeUtils,
tickerId = R.string.folder_download_worker_ticker_id,
channelId = NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD
) {

companion object {
private const val NOTIFICATION_ID = 391
Expand All @@ -42,11 +48,13 @@ class FolderDownloadWorkerNotificationManager(private val context: Context, view

if (progress != null) {
setProgress(MAX_PROGRESS, progress, false)
addAction(
R.drawable.ic_cancel,
context.getString(R.string.common_cancel),
getCancelPendingIntent()
)
if (showCancel) {
addAction(
R.drawable.ic_cancel,
context.getString(R.string.common_cancel),
getCancelPendingIntent()
)
}
} else {
setProgress(0, 0, false)
}
Expand All @@ -66,35 +74,36 @@ class FolderDownloadWorkerNotificationManager(private val context: Context, view
}

@Suppress("MagicNumber")
fun getProgressNotification(
folderName: String,
filename: String,
currentIndex: Int,
totalFileSize: Int
): Notification {
val currentFileIndex = (currentIndex + 1)
val description = context.getString(R.string.folder_download_counter, currentFileIndex, totalFileSize, filename)
val progress = (currentFileIndex * MAX_PROGRESS) / totalFileSize
return getNotification(folderName, description, progress)
fun showProgressNotification(folderName: String, filename: String, currentIndex: Int, totalFileSize: Int) {
mainThread(delay = 0) {
val currentFileIndex = (currentIndex + 1)
val description =
context.getString(R.string.folder_download_counter, currentFileIndex, totalFileSize, filename)
val progress = (currentFileIndex * MAX_PROGRESS) / totalFileSize
val notification = getNotification(folderName, description, progress)
showNotification(notification)
}
}

fun showCompletionNotification(folderName: String, success: Boolean) {
val titleId = if (success) {
R.string.folder_download_success_notification_title
} else {
R.string.folder_download_error_notification_title
}
mainThread(delay = 0) {
val titleId = if (success) {
R.string.folder_download_success_notification_title
} else {
R.string.folder_download_error_notification_title
}

val title = context.getString(titleId, folderName)
val title = context.getString(titleId, folderName)

val notification = getNotification(title)
notificationManager.notify(NOTIFICATION_ID, notification)
val notification = getNotification(title)
notificationManager.notify(NOTIFICATION_ID, notification)
}
}

fun showNotAvailableDiskSpace() {
suspend fun showNotAvailableDiskSpace() = withContext(Dispatchers.Main) {
val title = context.getString(R.string.folder_download_insufficient_disk_space_notification_title)
val notification = getNotification(title)
notificationManager.notify(NOTIFICATION_ID, notification)
notificationManager.notify(Random.nextInt(), notification)
}

fun getForegroundInfo(folder: OCFile?): ForegroundInfo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import com.owncloud.android.utils.theme.ViewThemeUtils
open class WorkerNotificationManager(
private val id: Int,
private val context: Context,
viewThemeUtils: ViewThemeUtils,
viewThemeUtils: ViewThemeUtils?,
private val tickerId: Int,
channelId: String
) {
Expand All @@ -42,7 +42,7 @@ open class WorkerNotificationManager(
setVibrate(null)
setOnlyAlertOnce(true)
setSilent(true)
viewThemeUtils.androidx.themeNotificationCompatBuilder(context, this)
viewThemeUtils?.androidx?.themeNotificationCompatBuilder(context, this)
}

fun showNotification() {
Expand Down
Loading
Loading