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
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,6 @@ interface BackgroundJobManager {
fun scheduleInternal2WaySync(intervalMinutes: Long)
fun cancelAllFilesDownloadJobs()
fun startMetadataSyncJob(currentDirPath: String)
fun downloadFolder(folder: OCFile, accountName: String)
fun downloadFolder(folder: OCFile, accountName: String, syncAll: Boolean)
fun cancelFolderDownload()
}
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@ internal class BackgroundJobManagerImpl(
workManager.enqueueUniquePeriodicWork(JOB_INTERNAL_TWO_WAY_SYNC, ExistingPeriodicWorkPolicy.UPDATE, request)
}

override fun downloadFolder(folder: OCFile, accountName: String) {
override fun downloadFolder(folder: OCFile, accountName: String, syncAll: Boolean) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
Expand All @@ -794,6 +794,7 @@ internal class BackgroundJobManagerImpl(
val data = Data.Builder()
.putLong(FolderDownloadWorker.FOLDER_ID, folder.fileId)
.putString(FolderDownloadWorker.ACCOUNT_NAME, accountName)
.putBoolean(FolderDownloadWorker.SYNC_ALL, syncAll)
.build()

val request = oneTimeRequestBuilder(FolderDownloadWorker::class, JOB_DOWNLOAD_FOLDER)
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,
true,
false
)
val operationResult = operation?.execute(context)

if (operationResult?.isSuccess == true) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,12 @@ class FileDownloadHelper {
)
}

fun downloadFolder(folder: OCFile?, accountName: String) {
fun downloadFolder(folder: OCFile?, accountName: String, syncAll: Boolean) {
if (folder == null) {
Log_OC.e(TAG, "folder cannot be null, cant sync")
return
}
backgroundJobManager.downloadFolder(folder, accountName)
backgroundJobManager.downloadFolder(folder, accountName, syncAll)
}

fun cancelFolderDownload() = backgroundJobManager.cancelFolderDownload()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ 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

Expand All @@ -39,9 +40,8 @@ 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>()

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

Expand All @@ -58,42 +58,65 @@ 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 syncAll = inputData.getBoolean(SYNC_ALL, false)
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} | syncAll=$syncAll")

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)

if (syncAll) {
Log_OC.d(TAG, "checking available disk space for full recursive download")
if (!FileStorageUtils.checkIfEnoughSpace(folder)) {
notificationManager.showNotAvailableDiskSpace()
return@withContext Result.failure()
}
}

val files = getFiles(folder, storageManager, syncAll)
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()
}

if (!FileStorageUtils.checkIfEnoughSpace(folder)) {
notificationManager.showNotAvailableDiskSpace()
return@withContext Result.failure()
}

Expand All @@ -105,41 +128,43 @@ class FolderDownloadWorker(
files.size
)
notificationManager.showNotification(notification)

val foregroundInfo = notificationManager.getForegroundInfo(notification)
setForeground(foregroundInfo)
setForeground(notificationManager.getForegroundInfo(notification))
}

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 +180,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 +195,25 @@ 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
private fun getFiles(folder: OCFile, storageManager: FileDataStorageManager, syncAll: Boolean): List<OCFile> =
if (syncAll) {
storageManager.getAllFilesRecursivelyInsideFolder(folder)
.filter { !it.isDown }
} else {
true
storageManager.getFolderContent(folder, false)
.filter { !it.isFolder && !it.isDown }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ 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) :
Expand Down Expand Up @@ -78,7 +80,7 @@ class FolderDownloadWorkerNotificationManager(private val context: Context, view
return getNotification(folderName, description, progress)
}

fun showCompletionNotification(folderName: String, success: Boolean) {
suspend fun showCompletionNotification(folderName: String, success: Boolean) = withContext(Dispatchers.Main) {
val titleId = if (success) {
R.string.folder_download_success_notification_title
} else {
Expand All @@ -91,10 +93,10 @@ class FolderDownloadWorkerNotificationManager(private val context: Context, view
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
12 changes: 10 additions & 2 deletions app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ enum class FileAction(
// Uploads and downloads
DOWNLOAD_FILE(R.id.action_download_file, R.string.filedetails_download, R.drawable.ic_cloud_download),
DOWNLOAD_FOLDER(R.id.action_sync_file, R.string.filedetails_sync_file, R.drawable.ic_sync),
DOWNLOAD_ALL_FOLDERS(R.id.action_sync_all_files, R.string.filedetails_sync_all_files, R.drawable.ic_sync_all),
CANCEL_SYNC(R.id.action_cancel_sync, R.string.common_cancel_sync, R.drawable.ic_sync_off),

// File sharing
Expand Down Expand Up @@ -96,6 +97,10 @@ enum class FileAction(
PIN_TO_HOMESCREEN,
RETRY
).apply {
if (files.size == 1 && files.first().isFolder && !files.first().isEncrypted) {
add(DOWNLOAD_ALL_FOLDERS)
}

val deleteOrLeaveShareAction = getDeleteOrLeaveShareAction(files) ?: return@apply
add(deleteOrLeaveShareAction)
}
Expand All @@ -113,7 +118,7 @@ enum class FileAction(

if (file != null) {
val actionsToHide = getActionsToHide(setOf(file))
result.removeAll(actionsToHide)
result.removeAll(actionsToHide.toSet())
}

return result.toList()
Expand All @@ -136,6 +141,7 @@ enum class FileAction(
if (file?.isFolder == true) {
result.add(R.id.action_send_file)
result.add(R.id.action_sync_file)
result.add(R.id.action_sync_all_files)
}

if (file?.isAPKorAAB == true) {
Expand All @@ -145,7 +151,7 @@ enum class FileAction(

if (file != null) {
val actionsToHide = getActionsToHide(setOf(file))
result.removeAll(actionsToHide)
result.removeAll(actionsToHide.toSet())
}

return result.toList()
Expand All @@ -160,6 +166,7 @@ enum class FileAction(
R.id.action_favorite,
R.id.action_move_or_copy,
R.id.action_sync_file,
R.id.action_sync_all_files,
R.id.action_encrypted,
R.id.action_unset_encrypted,
R.id.action_edit,
Expand All @@ -176,6 +183,7 @@ enum class FileAction(
R.id.action_send_share_file,
R.id.action_export_file,
R.id.action_sync_file,
R.id.action_sync_all_files,
R.id.action_download_file
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ private void filterSync(List<Integer> toHide, boolean synchronizing) {
if (files.isEmpty() || (!anyFileDown() && !containsFolder()) || synchronizing || containsEncryptedFile()
|| containsEncryptedFolder()) {
toHide.add(R.id.action_sync_file);
toHide.add(R.id.action_sync_all_files);
}
}

Expand Down
Loading
Loading