Skip to content

Commit 3d0d38e

Browse files
Merge pull request #16502 from nextcloud/backport/16459/stable-3.36
[stable-3.36] fix(auto-upload): file detection
2 parents 137b4eb + 9e64e92 commit 3d0d38e

13 files changed

Lines changed: 318 additions & 273 deletions

File tree

app/src/main/java/com/nextcloud/client/database/dao/FileSystemDao.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.nextcloud.client.database.dao
99

1010
import androidx.room.Dao
11+
import androidx.room.Delete
1112
import androidx.room.Insert
1213
import androidx.room.OnConflictStrategy
1314
import androidx.room.Query
@@ -19,6 +20,9 @@ interface FileSystemDao {
1920
@Insert(onConflict = OnConflictStrategy.REPLACE)
2021
fun insertOrReplace(filesystemEntity: FilesystemEntity)
2122

23+
@Delete
24+
fun delete(entity: FilesystemEntity)
25+
2226
@Query(
2327
"""
2428
DELETE FROM ${ProviderMeta.ProviderTableMeta.FILESYSTEM_TABLE_NAME}

app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ interface UploadDao {
4141
"WHERE ${ProviderTableMeta.UPLOADS_ACCOUNT_NAME} = :accountName " +
4242
"AND ${ProviderTableMeta.UPLOADS_REMOTE_PATH} = :remotePath"
4343
)
44-
fun deleteByAccountAndRemotePath(remotePath: String, accountName: String)
44+
fun deleteByRemotePathAndAccountName(remotePath: String, accountName: String)
4545

4646
@Query(
4747
"SELECT * FROM " + ProviderTableMeta.UPLOADS_TABLE_NAME +
@@ -51,7 +51,7 @@ interface UploadDao {
5151
)
5252
fun getUploadById(id: Long, accountName: String): UploadEntity?
5353

54-
@Insert(onConflict = OnConflictStrategy.Companion.REPLACE)
54+
@Insert(onConflict = OnConflictStrategy.REPLACE)
5555
fun insertOrReplace(entity: UploadEntity): Long
5656

5757
@Query(

app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ class BackgroundJobFactory @Inject constructor(
179179
powerManagementService = powerManagementService,
180180
syncedFolderProvider = syncedFolderProvider,
181181
backgroundJobManager = backgroundJobManager.get(),
182-
repository = FileSystemRepository(dao = database.fileSystemDao(), context),
182+
repository = FileSystemRepository(dao = database.fileSystemDao(), uploadsStorageManager, context),
183183
viewThemeUtils = viewThemeUtils.get(),
184184
localBroadcastManager = localBroadcastManager.get()
185185
)

app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt

Lines changed: 7 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ package com.nextcloud.client.jobs.autoUpload
99

1010
import android.app.Notification
1111
import android.content.Context
12-
import android.content.res.Resources
13-
import androidx.exifinterface.media.ExifInterface
1412
import androidx.localbroadcastmanager.content.LocalBroadcastManager
1513
import androidx.work.CoroutineWorker
1614
import androidx.work.ForegroundInfo
@@ -25,13 +23,11 @@ import com.nextcloud.client.jobs.upload.FileUploadBroadcastManager
2523
import com.nextcloud.client.jobs.upload.FileUploadWorker
2624
import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager
2725
import com.nextcloud.client.network.ConnectivityService
28-
import com.nextcloud.client.preferences.SubFolderRule
2926
import com.nextcloud.utils.extensions.isNonRetryable
3027
import com.nextcloud.utils.extensions.updateStatus
3128
import com.owncloud.android.R
3229
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
3330
import com.owncloud.android.datamodel.FileDataStorageManager
34-
import com.owncloud.android.datamodel.MediaFolderType
3531
import com.owncloud.android.datamodel.SyncedFolder
3632
import com.owncloud.android.datamodel.SyncedFolderProvider
3733
import com.owncloud.android.datamodel.UploadsStorageManager
@@ -44,16 +40,10 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult
4440
import com.owncloud.android.lib.common.utils.Log_OC
4541
import com.owncloud.android.operations.UploadFileOperation
4642
import com.owncloud.android.ui.activity.SettingsActivity
47-
import com.owncloud.android.utils.FileStorageUtils
48-
import com.owncloud.android.utils.MimeType
4943
import com.owncloud.android.utils.theme.ViewThemeUtils
5044
import kotlinx.coroutines.Dispatchers
5145
import kotlinx.coroutines.withContext
5246
import java.io.File
53-
import java.text.ParsePosition
54-
import java.text.SimpleDateFormat
55-
import java.util.Locale
56-
import java.util.TimeZone
5747

5848
@Suppress("LongParameterList", "TooManyFunctions", "TooGenericExceptionCaught")
5949
class AutoUploadWorker(
@@ -79,6 +69,7 @@ class AutoUploadWorker(
7969
}
8070

8171
private val helper = AutoUploadHelper()
72+
private val syncFolderHelper = SyncFolderHelper(context)
8273
private val fileUploadBroadcastManager = FileUploadBroadcastManager(localBroadcastManager)
8374
private lateinit var syncedFolder: SyncedFolder
8475
private val notificationManager = AutoUploadNotificationManager(context, viewThemeUtils, NOTIFICATION_ID)
@@ -102,7 +93,11 @@ class AutoUploadWorker(
10293
collectFileChangesFromContentObserverWork(contentUris)
10394
uploadFiles(syncedFolder)
10495

105-
Log_OC.d(TAG, "${syncedFolder.remotePath} finished checking files.")
96+
// only update last scan time after uploading files
97+
syncedFolder.lastScanTimestampMs = System.currentTimeMillis()
98+
syncedFolderProvider.updateSyncFolder(syncedFolder)
99+
100+
Log_OC.d(TAG, "${syncedFolder.remotePath} completed")
106101
Result.success()
107102
} catch (e: Exception) {
108103
Log_OC.e(TAG, "❌ failed: ${e.message}")
@@ -226,20 +221,11 @@ class AutoUploadWorker(
226221
helper.insertEntries(syncedFolder, repository)
227222
}
228223
}
229-
syncedFolder.lastScanTimestampMs = System.currentTimeMillis()
230-
syncedFolderProvider.updateSyncFolder(syncedFolder)
231224
}
232225
} catch (e: Exception) {
233226
Log_OC.d(TAG, "Exception collectFileChangesFromContentObserverWork: $e")
234227
}
235228

236-
private fun prepareDateFormat(): SimpleDateFormat {
237-
val currentLocale = context.resources.configuration.locales[0]
238-
return SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale).apply {
239-
timeZone = TimeZone.getTimeZone(TimeZone.getDefault().id)
240-
}
241-
}
242-
243229
private fun getUserOrReturn(syncedFolder: SyncedFolder): User? {
244230
val optionalUser = userAccountManager.getUser(syncedFolder.account)
245231
if (!optionalUser.isPresent) {
@@ -274,13 +260,10 @@ class AutoUploadWorker(
274260

275261
@Suppress("LongMethod", "DEPRECATION", "TooGenericExceptionCaught")
276262
private suspend fun uploadFiles(syncedFolder: SyncedFolder) = withContext(Dispatchers.IO) {
277-
val dateFormat = prepareDateFormat()
278263
val user = getUserOrReturn(syncedFolder) ?: return@withContext
279264
val ocAccount = OwnCloudAccount(user.toPlatformAccount(), context)
280265
val client = OwnCloudClientManagerFactory.getDefaultSingleton()
281266
.getClientFor(ocAccount, context)
282-
val lightVersion = context.resources.getBoolean(R.bool.syncedFolder_light)
283-
val currentLocale = context.resources.configuration.locales[0]
284267

285268
trySetForeground()
286269
updateNotification()
@@ -299,14 +282,7 @@ class AutoUploadWorker(
299282
filePathsWithIds.forEachIndexed { batchIndex, (path, id) ->
300283
val file = File(path)
301284
val localPath = file.absolutePath
302-
val remotePath = getRemotePath(
303-
file,
304-
syncedFolder,
305-
dateFormat,
306-
lightVersion,
307-
context.resources,
308-
currentLocale
309-
)
285+
val remotePath = syncFolderHelper.getAutoUploadRemotePath(syncedFolder, file)
310286

311287
try {
312288
val entityResult = getEntityResult(user, localPath, remotePath)
@@ -337,7 +313,6 @@ class AutoUploadWorker(
337313

338314
val result = operation.execute(client)
339315
fileUploadBroadcastManager.sendStarted(operation, context)
340-
uploadsStorageManager.updateStatus(uploadEntity, result.isSuccess)
341316

342317
UploadErrorNotificationManager.handleResult(
343318
context,
@@ -471,79 +446,6 @@ class AutoUploadWorker(
471446
FileDataStorageManager(user, context.contentResolver)
472447
)
473448

474-
private fun getRemotePath(
475-
file: File,
476-
syncedFolder: SyncedFolder,
477-
sFormatter: SimpleDateFormat,
478-
lightVersion: Boolean,
479-
resources: Resources,
480-
currentLocale: Locale
481-
): String {
482-
val lastModificationTime = calculateLastModificationTime(file, syncedFolder, sFormatter)
483-
484-
val (remoteFolder, useSubfolders, subFolderRule) = if (lightVersion) {
485-
Triple(
486-
resources.getString(R.string.syncedFolder_remote_folder),
487-
resources.getBoolean(R.bool.syncedFolder_light_use_subfolders),
488-
SubFolderRule.YEAR_MONTH
489-
)
490-
} else {
491-
Triple(
492-
syncedFolder.remotePath,
493-
syncedFolder.isSubfolderByDate,
494-
syncedFolder.subfolderRule
495-
)
496-
}
497-
498-
return FileStorageUtils.getInstantUploadFilePath(
499-
file,
500-
currentLocale,
501-
remoteFolder,
502-
syncedFolder.localPath,
503-
lastModificationTime,
504-
useSubfolders,
505-
subFolderRule
506-
)
507-
}
508-
509-
private fun hasExif(file: File): Boolean {
510-
val mimeType = FileStorageUtils.getMimeTypeFromName(file.absolutePath)
511-
return MimeType.JPEG.equals(mimeType, ignoreCase = true) || MimeType.TIFF.equals(mimeType, ignoreCase = true)
512-
}
513-
514-
@Suppress("NestedBlockDepth")
515-
private fun calculateLastModificationTime(
516-
file: File,
517-
syncedFolder: SyncedFolder,
518-
formatter: SimpleDateFormat
519-
): Long {
520-
var lastModificationTime = file.lastModified()
521-
if (MediaFolderType.IMAGE == syncedFolder.type && hasExif(file)) {
522-
Log_OC.d(TAG, "calculateLastModificationTime exif found")
523-
524-
@Suppress("TooGenericExceptionCaught")
525-
try {
526-
val exifInterface = ExifInterface(file.absolutePath)
527-
val exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME)
528-
if (!exifDate.isNullOrBlank()) {
529-
val pos = ParsePosition(0)
530-
val dateTime = formatter.parse(exifDate, pos)
531-
if (dateTime != null) {
532-
lastModificationTime = dateTime.time
533-
Log_OC.w(TAG, "calculateLastModificationTime calculatedTime is: $lastModificationTime")
534-
} else {
535-
Log_OC.w(TAG, "calculateLastModificationTime dateTime is empty")
536-
}
537-
} else {
538-
Log_OC.w(TAG, "calculateLastModificationTime exifDate is empty")
539-
}
540-
} catch (e: Exception) {
541-
Log_OC.d(TAG, "Failed to get the proper time " + e.localizedMessage)
542-
}
543-
}
544-
return lastModificationTime
545-
}
546-
547449
private fun sendUploadFinishEvent(operation: UploadFileOperation, result: RemoteOperationResult<*>) {
548450
fileUploadBroadcastManager.sendFinished(
549451
operation,

app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,37 @@ import com.nextcloud.client.database.entity.FilesystemEntity
1515
import com.nextcloud.utils.extensions.shouldSkipFile
1616
import com.nextcloud.utils.extensions.toFile
1717
import com.owncloud.android.datamodel.SyncedFolder
18+
import com.owncloud.android.datamodel.UploadsStorageManager
1819
import com.owncloud.android.lib.common.utils.Log_OC
1920
import com.owncloud.android.utils.SyncedFolderUtils
2021
import java.io.File
2122
import java.util.zip.CRC32
2223

2324
@Suppress("TooGenericExceptionCaught", "NestedBlockDepth", "MagicNumber", "ReturnCount")
24-
class FileSystemRepository(private val dao: FileSystemDao, private val context: Context) {
25+
class FileSystemRepository(
26+
private val dao: FileSystemDao,
27+
private val uploadsStorageManager: UploadsStorageManager,
28+
private val context: Context
29+
) {
30+
private val syncFolderHelper = SyncFolderHelper(context)
2531

2632
companion object {
2733
private const val TAG = "FilesystemRepository"
2834
const val BATCH_SIZE = 50
2935
}
3036

37+
fun deleteAutoUploadAndUploadEntity(syncedFolder: SyncedFolder, localPath: String, entity: FilesystemEntity) {
38+
Log_OC.d(TAG, "deleting auto upload entity and upload entity")
39+
40+
val file = File(localPath)
41+
val remotePath = syncFolderHelper.getAutoUploadRemotePath(syncedFolder, file)
42+
uploadsStorageManager.uploadDao.deleteByRemotePathAndAccountName(
43+
remotePath = remotePath,
44+
accountName = syncedFolder.account
45+
)
46+
dao.delete(entity)
47+
}
48+
3149
suspend fun deleteByLocalPathAndId(path: String, id: Int) {
3250
dao.deleteByLocalPathAndId(path, id)
3351
}
@@ -39,20 +57,23 @@ class FileSystemRepository(private val dao: FileSystemDao, private val context:
3957
val entities = dao.getAutoUploadFilesEntities(syncedFolderId, BATCH_SIZE, lastId)
4058
val filtered = mutableListOf<Pair<String, Int>>()
4159

42-
entities.forEach {
43-
it.localPath?.let { path ->
60+
entities.forEach { entity ->
61+
entity.localPath?.let { path ->
4462
val file = File(path)
4563
if (!file.exists()) {
4664
Log_OC.w(TAG, "Ignoring file for upload (doesn't exist): $path")
65+
deleteAutoUploadAndUploadEntity(syncedFolder, path, entity)
4766
} else if (!SyncedFolderUtils.isQualifiedFolder(file.parent)) {
4867
Log_OC.w(TAG, "Ignoring file for upload (unqualified folder): $path")
68+
deleteAutoUploadAndUploadEntity(syncedFolder, path, entity)
4969
} else if (!SyncedFolderUtils.isFileNameQualifiedForAutoUpload(file.name)) {
5070
Log_OC.w(TAG, "Ignoring file for upload (unqualified file): $path")
71+
deleteAutoUploadAndUploadEntity(syncedFolder, path, entity)
5172
} else {
5273
Log_OC.d(TAG, "Adding path to upload: $path")
5374

54-
if (it.id != null) {
55-
filtered.add(path to it.id)
75+
if (entity.id != null) {
76+
filtered.add(path to entity.id)
5677
} else {
5778
Log_OC.w(TAG, "cant adding path to upload, id is null")
5879
}
@@ -160,22 +181,21 @@ class FileSystemRepository(private val dao: FileSystemDao, private val context:
160181
}
161182

162183
val entity = dao.getFileByPathAndFolder(localPath, syncedFolder.id.toString())
163-
val fileSentForUpload = (entity != null && entity.fileSentForUpload == 1)
164-
if (fileSentForUpload) {
165-
Log_OC.d(TAG, "File was sent for upload, checking if it changed...")
166-
}
167184

168185
val fileModified = (lastModified ?: file.lastModified())
169-
if (syncedFolder.shouldSkipFile(file, fileModified, creationTime, fileSentForUpload)) {
186+
val hasNotChanged = entity?.fileModified == fileModified
187+
val fileSentForUpload = entity?.fileSentForUpload == 1
188+
189+
if (hasNotChanged && fileSentForUpload) {
190+
Log_OC.d(TAG, "File hasn't changed since last scan. skipping: $localPath")
170191
return
171192
}
172193

173-
if (fileSentForUpload) {
174-
Log_OC.d(TAG, "File was sent for upload before but has changed, will re-upload: $localPath")
194+
if (syncedFolder.shouldSkipFile(file, fileModified, creationTime, fileSentForUpload)) {
195+
return
175196
}
176197

177198
val crc = getFileChecksum(file)
178-
179199
val newEntity = FilesystemEntity(
180200
id = entity?.id,
181201
localPath = localPath,

0 commit comments

Comments
 (0)