diff --git a/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt b/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt index 06a94aaafdff..86d02064c7a3 100644 --- a/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt +++ b/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt @@ -79,7 +79,7 @@ interface FileDao { AND file_owner = :accountName AND is_encrypted = 0 AND (content_type = :dirType OR content_type = :webdavType) - ORDER BY ${ProviderTableMeta.FILE_DEFAULT_SORT_ORDER} + ORDER BY ${ProviderTableMeta._ID} ASC """ ) fun getNonEncryptedSubfolders( diff --git a/app/src/main/java/com/nextcloud/client/jobs/metadata/MetadataWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/metadata/MetadataWorker.kt index b6e19b318846..057268c7287f 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/metadata/MetadataWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/metadata/MetadataWorker.kt @@ -19,6 +19,7 @@ import com.owncloud.android.operations.RefreshFolderOperation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +@Suppress("DEPRECATION", "ReturnCount", "TooGenericExceptionCaught") class MetadataWorker(private val context: Context, params: WorkerParameters, private val user: User) : CoroutineWorker(context, params) { @@ -27,36 +28,65 @@ class MetadataWorker(private val context: Context, params: WorkerParameters, pri const val FILE_PATH = "file_path" } - @Suppress("DEPRECATION", "ReturnCount") override suspend fun doWork(): Result { - val storageManager = FileDataStorageManager(user, context.contentResolver) val filePath = inputData.getString(FILE_PATH) if (filePath == null) { Log_OC.e(TAG, "❌ Invalid folder path. Aborting metadata sync. $filePath") return Result.failure() } + + val storageManager = FileDataStorageManager(user, context.contentResolver) val currentDir = storageManager.getFileByDecryptedRemotePath(filePath) if (currentDir == null) { Log_OC.e(TAG, "❌ Current directory is null. Aborting metadata sync. $filePath") return Result.failure() } - Log_OC.d(TAG, "🕒 Starting metadata sync for folder: $filePath") + if (!currentDir.hasValidParentId()) { + Log_OC.e(TAG, "❌ Current directory has invalid ID: ${currentDir.fileId}. Path: $filePath") + return Result.failure() + } + + Log_OC.d(TAG, "🕒 Starting metadata sync for folder: $filePath, id: ${currentDir.fileId}") + + // First check current dir + val currentRefreshResult = refreshFolder(currentDir, storageManager) + if (!currentRefreshResult) { + Log_OC.e(TAG, "❌ Failed to refresh current directory: $filePath") + return Result.failure() + } - // first check current dir - refreshFolder(currentDir, storageManager) + // Re-fetch the folder after refresh to get updated data + val refreshedDir = storageManager.getFileByPath(filePath) + if (refreshedDir == null || !refreshedDir.hasValidParentId()) { + Log_OC.e(TAG, "❌ Directory invalid after refresh. Path: $filePath") + return Result.failure() + } // then get up-to-date subfolders - val subfolders = storageManager.getNonEncryptedSubfolders(currentDir.fileId, user.accountName) + val subfolders = storageManager.getNonEncryptedSubfolders(refreshedDir.fileId, user.accountName) + Log_OC.d(TAG, "Found ${subfolders.size} subfolders to sync") + + var failedCount = 0 subfolders.forEach { subFolder -> - refreshFolder(subFolder, storageManager) + if (!subFolder.hasValidParentId()) { + Log_OC.e(TAG, "❌ Skipping subfolder with invalid ID: ${subFolder.remotePath}") + failedCount++ + return@forEach + } + + val success = refreshFolder(subFolder, storageManager) + if (!success) { + failedCount++ + } } - Log_OC.d(TAG, "🏁 Metadata sync completed for folder: $filePath") + Log_OC.d(TAG, "🏁 Metadata sync completed for folder: $filePath. Failed: $failedCount/${subfolders.size}") + return Result.success() } @Suppress("DEPRECATION") - private suspend fun refreshFolder(folder: OCFile, storageManager: FileDataStorageManager) = + private suspend fun refreshFolder(folder: OCFile, storageManager: FileDataStorageManager): Boolean = withContext(Dispatchers.IO) { Log_OC.d( TAG, @@ -65,19 +95,31 @@ class MetadataWorker(private val context: Context, params: WorkerParameters, pri " eTag: " + folder.etag + "\n" + " eTagOnServer: " + folder.etagOnServer ) + if (!folder.hasValidParentId()) { + Log_OC.e(TAG, "❌ Folder has invalid ID: ${folder.remotePath}") + return@withContext false + } + if (!folder.isEtagChanged) { Log_OC.d(TAG, "Skipping ${folder.remotePath}, eTag didn't change") - return@withContext + return@withContext true } - Log_OC.d(TAG, "⏳ Fetching metadata for: ${folder.remotePath}") + Log_OC.d(TAG, "⏳ Fetching metadata for: ${folder.remotePath}, id: ${folder.fileId}") val operation = RefreshFolderOperation(folder, storageManager, user, context) - val result = operation.execute(user, context) - if (result.isSuccess) { - Log_OC.d(TAG, "✅ Successfully fetched metadata for: ${folder.remotePath}") - } else { - Log_OC.e(TAG, "❌ Failed to fetch metadata for: ${folder.remotePath}") + return@withContext try { + val result = operation.execute(user, context) + if (result.isSuccess) { + Log_OC.d(TAG, "✅ Successfully fetched metadata for: ${folder.remotePath}") + true + } else { + Log_OC.e(TAG, "❌ Failed to fetch metadata for: ${folder.remotePath}") + false + } + } catch (e: Exception) { + Log_OC.e(TAG, "❌ Exception refreshing folder ${folder.remotePath}: ${e.message}", e) + false } } } diff --git a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java index d9db1173002f..03d85e22a902 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java +++ b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java @@ -1166,4 +1166,13 @@ public void setIsRecommendedFile(boolean value) { public boolean isRecommendedFile() { return recommendedFile; } + + // only root directories parent id can be 0 + public boolean hasValidParentId() { + if (isRootDirectory()) { + return getParentId() == 0; + } else { + return getParentId() != 0; + } + } }