Skip to content

Commit 19b97e8

Browse files
Merge pull request #16585 from nextcloud/fix/is-auto-upload-anr
fix(anr): is auto upload
2 parents 1e8aa7e + 9e8367c commit 19b97e8

File tree

8 files changed

+115
-101
lines changed

8 files changed

+115
-101
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import androidx.room.Dao
1111
import androidx.room.Query
1212
import com.nextcloud.client.database.entity.SyncedFolderEntity
1313
import com.owncloud.android.db.ProviderMeta
14+
import kotlinx.coroutines.flow.Flow
1415

1516
@Dao
1617
interface SyncedFolderDao {
@@ -23,4 +24,7 @@ interface SyncedFolderDao {
2324
"""
2425
)
2526
fun findByLocalPathAndAccount(localPath: String, account: String): SyncedFolderEntity?
27+
28+
@Query("SELECT * FROM ${ProviderMeta.ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME}")
29+
fun getAllAsFlow(): Flow<List<SyncedFolderEntity>>
2630
}

app/src/main/java/com/nextcloud/utils/ShortcutUtil.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import androidx.core.graphics.drawable.toDrawable
2424
import com.nextcloud.client.account.User
2525
import com.owncloud.android.R
2626
import com.owncloud.android.datamodel.OCFile
27+
import com.owncloud.android.datamodel.SyncedFolderObserver
2728
import com.owncloud.android.datamodel.SyncedFolderProvider
2829
import com.owncloud.android.datamodel.ThumbnailsCacheManager
2930
import com.owncloud.android.ui.activity.FileActivity
@@ -91,7 +92,7 @@ class ShortcutUtil @Inject constructor(private val mContext: Context) {
9192
thumbnail != null -> IconCompat.createWithAdaptiveBitmap(bitmapToAdaptiveBitmap(thumbnail))
9293

9394
file.isFolder -> {
94-
val isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user)
95+
val isAutoUploadFolder = SyncedFolderObserver.isAutoUploadFolder(file, user)
9596
val isDarkModeActive = syncedFolderProvider.preferences.isDarkModeEnabled
9697
val overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder)
9798
val drawable = MimeTypeUtil.getFolderIcon(isDarkModeActive, overlayIconId, mContext, viewThemeUtils)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <[email protected]>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.owncloud.android.datamodel
9+
10+
import com.nextcloud.client.account.User
11+
import com.nextcloud.client.database.dao.SyncedFolderDao
12+
import com.owncloud.android.lib.resources.files.model.ServerFileInterface
13+
import kotlinx.coroutines.CoroutineScope
14+
import kotlinx.coroutines.Dispatchers
15+
import kotlinx.coroutines.Job
16+
import kotlinx.coroutines.SupervisorJob
17+
import kotlinx.coroutines.flow.distinctUntilChanged
18+
import kotlinx.coroutines.launch
19+
20+
object SyncedFolderObserver {
21+
22+
@Volatile
23+
private var syncedFoldersMap = mapOf<String, Set<String>>()
24+
25+
private var job: Job? = null
26+
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
27+
28+
fun start(dao: SyncedFolderDao) {
29+
if (job?.isActive == true) return
30+
31+
job = scope.launch {
32+
dao.getAllAsFlow()
33+
.distinctUntilChanged()
34+
.collect { updatedEntities ->
35+
syncedFoldersMap = updatedEntities
36+
.filter { it.remotePath != null && it.account != null }
37+
.groupBy { it.account!! }
38+
.mapValues { (_, entities) ->
39+
entities.map { it.remotePath!!.trimEnd('/') }.toSet()
40+
}
41+
}
42+
}
43+
}
44+
45+
@Suppress("ReturnCount")
46+
fun isAutoUploadFolder(file: ServerFileInterface, user: User): Boolean {
47+
val accountFolders = syncedFoldersMap[user.accountName] ?: return false
48+
val normalizedRemotePath = file.remotePath.trimEnd('/')
49+
if (normalizedRemotePath.isEmpty()) return false
50+
return accountFolders.any { entityPath ->
51+
normalizedRemotePath == entityPath
52+
}
53+
}
54+
}

app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java

Lines changed: 45 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import com.owncloud.android.MainApp;
2626
import com.owncloud.android.db.ProviderMeta;
2727
import com.owncloud.android.lib.common.utils.Log_OC;
28-
import com.owncloud.android.lib.resources.files.model.ServerFileInterface;
2928

3029
import java.io.File;
3130
import java.util.ArrayList;
@@ -61,6 +60,7 @@ public SyncedFolderProvider(ContentResolver contentResolver, AppPreferences pref
6160
mContentResolver = contentResolver;
6261
this.preferences = preferences;
6362
this.clock = clock;
63+
SyncedFolderObserver.INSTANCE.start(dao);
6464
}
6565

6666
/**
@@ -84,23 +84,19 @@ public long storeSyncedFolder(SyncedFolder syncedFolder) {
8484
}
8585
}
8686

87-
public static boolean isAutoUploadFolder(SyncedFolderProvider syncedFolderProvider, ServerFileInterface file, User user) {
88-
return syncedFolderProvider != null && syncedFolderProvider.findByRemotePathAndAccount(file.getRemotePath(), user);
89-
}
90-
9187
public int countEnabledSyncedFolders() {
9288
int count = 0;
9389
Cursor cursor = mContentResolver.query(
94-
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
95-
null,
96-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED + " = ?",
97-
new String[]{"1"},
98-
null
99-
);
90+
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
91+
null,
92+
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED + " = ?",
93+
new String[]{"1"},
94+
null
95+
);
10096

10197
if (cursor != null) {
102-
count = cursor.getCount();
103-
cursor.close();
98+
count = cursor.getCount();
99+
cursor.close();
104100
}
105101

106102
return count;
@@ -154,12 +150,12 @@ public int updateSyncedFolderEnabled(long id, Boolean enabled) {
154150

155151
int result = 0;
156152
Cursor cursor = mContentResolver.query(
157-
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
158-
null,
159-
ProviderMeta.ProviderTableMeta._ID + "=?",
160-
new String[]{String.valueOf(id)},
161-
null
162-
);
153+
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
154+
null,
155+
ProviderMeta.ProviderTableMeta._ID + "=?",
156+
new String[]{String.valueOf(id)},
157+
null
158+
);
163159

164160
if (cursor != null && cursor.getCount() == 1) {
165161
while (cursor.moveToNext()) {
@@ -177,7 +173,7 @@ public int updateSyncedFolderEnabled(long id, Boolean enabled) {
177173
Log_OC.e(TAG, "Sync folder db cursor for ID=" + id + " in NULL.");
178174
} else {
179175
Log_OC.e(TAG, cursor.getCount() + " items for id=" + id + " available in sync folder database. " +
180-
"Expected 1. Failed to update sync folder db.");
176+
"Expected 1. Failed to update sync folder db.");
181177
}
182178
}
183179

@@ -226,10 +222,10 @@ public SyncedFolder getSyncedFolderByID(Long syncedFolderID) {
226222
*/
227223
public int deleteSyncFoldersForAccount(User user) {
228224
return mContentResolver.delete(
229-
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
230-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " = ?",
231-
new String[]{String.valueOf(user.getAccountName())}
232-
);
225+
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
226+
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " = ?",
227+
new String[]{String.valueOf(user.getAccountName())}
228+
);
233229
}
234230

235231
/**
@@ -284,10 +280,10 @@ public void updateAutoUploadPaths(Context context) {
284280
*/
285281
public int deleteSyncedFoldersNotInList(List<Long> ids) {
286282
int result = mContentResolver.delete(
287-
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
288-
ProviderMeta.ProviderTableMeta._ID + " NOT IN (?)",
289-
new String[]{String.valueOf(ids)}
290-
);
283+
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
284+
ProviderMeta.ProviderTableMeta._ID + " NOT IN (?)",
285+
new String[]{String.valueOf(ids)}
286+
);
291287

292288
if(result > 0) {
293289
preferences.setLegacyClean(true);
@@ -301,10 +297,10 @@ public int deleteSyncedFoldersNotInList(List<Long> ids) {
301297
*/
302298
public int deleteSyncedFolder(long id) {
303299
return mContentResolver.delete(
304-
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
305-
ProviderMeta.ProviderTableMeta._ID + " = ?",
306-
new String[]{String.valueOf(id)}
307-
);
300+
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
301+
ProviderMeta.ProviderTableMeta._ID + " = ?",
302+
new String[]{String.valueOf(id)}
303+
);
308304
}
309305

310306
public AppPreferences getPreferences() {
@@ -323,11 +319,11 @@ public int updateSyncFolder(SyncedFolder syncedFolder) {
323319
ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder);
324320

325321
return mContentResolver.update(
326-
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
327-
cv,
328-
ProviderMeta.ProviderTableMeta._ID + "=?",
329-
new String[]{String.valueOf(syncedFolder.getId())}
330-
);
322+
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
323+
cv,
324+
ProviderMeta.ProviderTableMeta._ID + "=?",
325+
new String[]{String.valueOf(syncedFolder.getId())}
326+
);
331327
}
332328

333329
/**
@@ -341,33 +337,33 @@ private SyncedFolder createSyncedFolderFromCursor(Cursor cursor) {
341337
if (cursor != null) {
342338
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ProviderMeta.ProviderTableMeta._ID));
343339
String localPath = cursor.getString(cursor.getColumnIndexOrThrow(
344-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH));
340+
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH));
345341
String remotePath = cursor.getString(cursor.getColumnIndexOrThrow(
346-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH));
342+
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH));
347343
boolean wifiOnly = cursor.getInt(cursor.getColumnIndexOrThrow(
348-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY)) == 1;
344+
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY)) == 1;
349345
boolean chargingOnly = cursor.getInt(cursor.getColumnIndexOrThrow(
350-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1;
346+
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1;
351347
boolean existing = cursor.getInt(cursor.getColumnIndexOrThrow(
352-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_EXISTING)) == 1;
348+
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_EXISTING)) == 1;
353349
boolean subfolderByDate = cursor.getInt(cursor.getColumnIndexOrThrow(
354-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1;
350+
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1;
355351
String accountName = cursor.getString(cursor.getColumnIndexOrThrow(
356-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT));
352+
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT));
357353
int uploadAction = cursor.getInt(cursor.getColumnIndexOrThrow(
358-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION));
354+
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION));
359355
int nameCollisionPolicy = cursor.getInt(cursor.getColumnIndexOrThrow(
360356
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_NAME_COLLISION_POLICY));
361357
boolean enabled = cursor.getInt(cursor.getColumnIndexOrThrow(
362-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1;
358+
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1;
363359
long enabledTimestampMs = cursor.getLong(cursor.getColumnIndexOrThrow(
364-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS));
360+
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS));
365361
MediaFolderType type = MediaFolderType.getById(cursor.getInt(cursor.getColumnIndexOrThrow(
366-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE)));
362+
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE)));
367363
boolean hidden = cursor.getInt(cursor.getColumnIndexOrThrow(
368364
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_HIDDEN)) == 1;
369365
SubFolderRule subFolderRule = SubFolderRule.values()[cursor.getInt(
370-
cursor.getColumnIndexOrThrow(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_RULE))];
366+
cursor.getColumnIndexOrThrow(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_RULE))];
371367
boolean excludeHidden = cursor.getInt(cursor.getColumnIndexOrThrow(
372368
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_EXCLUDE_HIDDEN)) == 1;
373369
long lastScanTimestampMs = cursor.getLong(cursor.getColumnIndexOrThrow(
@@ -423,48 +419,4 @@ private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFol
423419
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LAST_SCAN_TIMESTAMP_MS, syncedFolder.getLastScanTimestampMs());
424420
return cv;
425421
}
426-
427-
/**
428-
* method to check if sync folder for the remote path exist in table or not
429-
*
430-
* @param remotePath to be check
431-
* @param user for which we are looking
432-
* @return <code>true</code> if exist, <code>false</code> otherwise
433-
*/
434-
public boolean findByRemotePathAndAccount(String remotePath, User user) {
435-
if (user == null) {
436-
return false;
437-
}
438-
439-
boolean result = false;
440-
441-
//if path ends with / then remove the last / to work the query right way
442-
//because the sub folders of synced folders will not have the slash at the end
443-
if (remotePath.endsWith("/")) {
444-
remotePath = remotePath.substring(0, remotePath.length() - 1);
445-
}
446-
447-
Cursor cursor = mContentResolver.query(
448-
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
449-
null,
450-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH + " LIKE ? AND " +
451-
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " =? ",
452-
new String[]{"%" + remotePath + "%", user.getAccountName()},
453-
null);
454-
455-
if (cursor != null && cursor.getCount() >= 1) {
456-
result = true;
457-
} else {
458-
if (cursor == null) {
459-
Log_OC.e(TAG, "Sync folder db cursor for remote path = " + remotePath + " in NULL.");
460-
}
461-
}
462-
463-
if (cursor != null) {
464-
cursor.close();
465-
}
466-
467-
return result;
468-
469-
}
470422
}

app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.owncloud.android.R;
3232
import com.owncloud.android.databinding.RichdocumentsWebviewBinding;
3333
import com.owncloud.android.datamodel.OCFile;
34+
import com.owncloud.android.datamodel.SyncedFolderObserver;
3435
import com.owncloud.android.datamodel.SyncedFolderProvider;
3536
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
3637
import com.owncloud.android.ui.asynctasks.TextEditorLoadUrlTask;
@@ -251,7 +252,7 @@ protected void setThumbnailView(final User user) {
251252
// Todo minimize: only icon by mimetype
252253
OCFile file = getFile();
253254
if (file.isFolder()) {
254-
boolean isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user);
255+
boolean isAutoUploadFolder = SyncedFolderObserver.INSTANCE.isAutoUploadFolder(file, user);
255256

256257
Integer overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder);
257258
LayerDrawable drawable = MimeTypeUtil.getFolderIcon(preferences.isDarkModeEnabled(), overlayIconId, this, viewThemeUtils);

app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.owncloud.android.R;
2222
import com.owncloud.android.databinding.ShareActivityBinding;
2323
import com.owncloud.android.datamodel.OCFile;
24+
import com.owncloud.android.datamodel.SyncedFolderObserver;
2425
import com.owncloud.android.datamodel.SyncedFolderProvider;
2526
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
2627
import com.owncloud.android.lib.common.operations.RemoteOperation;
@@ -63,14 +64,14 @@ protected void onCreate(Bundle savedInstanceState) {
6364

6465
OCFile file = getFile();
6566
Optional<User> optionalUser = getUser();
66-
if (!optionalUser.isPresent()) {
67+
if (optionalUser.isEmpty()) {
6768
finish();
6869
return;
6970
}
7071

7172
// Icon
7273
if (file.isFolder()) {
73-
boolean isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, optionalUser.get());
74+
boolean isAutoUploadFolder = SyncedFolderObserver.INSTANCE.isAutoUploadFolder(file, optionalUser.get());
7475

7576
Integer overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder);
7677
LayerDrawable drawable = MimeTypeUtil.getFolderIcon(preferences.isDarkModeEnabled(), overlayIconId, this, viewThemeUtils);

app/src/main/java/com/owncloud/android/ui/adapter/ReceiveExternalFilesAdapter.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import com.nextcloud.client.account.User
1818
import com.owncloud.android.databinding.UploaderListItemLayoutBinding
1919
import com.owncloud.android.datamodel.FileDataStorageManager
2020
import com.owncloud.android.datamodel.OCFile
21+
import com.owncloud.android.datamodel.SyncedFolderObserver
2122
import com.owncloud.android.datamodel.SyncedFolderProvider
2223
import com.owncloud.android.datamodel.ThumbnailsCacheManager
2324
import com.owncloud.android.datamodel.ThumbnailsCacheManager.AsyncThumbnailDrawable
@@ -113,7 +114,7 @@ class ReceiveExternalFilesAdapter(
113114
}
114115

115116
private fun setupThumbnailForFolder(thumbnailImageView: ImageView, file: OCFile) {
116-
val isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user)
117+
val isAutoUploadFolder = SyncedFolderObserver.isAutoUploadFolder(file, user)
117118
val isDarkModeActive = syncedFolderProvider.preferences.isDarkModeEnabled
118119
val overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder)
119120
val icon = MimeTypeUtil.getFolderIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils)

0 commit comments

Comments
 (0)