Skip to content
Merged
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 @@ -11,6 +11,7 @@ import androidx.room.Dao
import androidx.room.Query
import com.nextcloud.client.database.entity.SyncedFolderEntity
import com.owncloud.android.db.ProviderMeta
import kotlinx.coroutines.flow.Flow

@Dao
interface SyncedFolderDao {
Expand All @@ -23,4 +24,7 @@ interface SyncedFolderDao {
"""
)
fun findByLocalPathAndAccount(localPath: String, account: String): SyncedFolderEntity?

@Query("SELECT * FROM ${ProviderMeta.ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME}")
fun getAllAsFlow(): Flow<List<SyncedFolderEntity>>
}
3 changes: 2 additions & 1 deletion app/src/main/java/com/nextcloud/utils/ShortcutUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.core.graphics.drawable.toDrawable
import com.nextcloud.client.account.User
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.SyncedFolderObserver
import com.owncloud.android.datamodel.SyncedFolderProvider
import com.owncloud.android.datamodel.ThumbnailsCacheManager
import com.owncloud.android.ui.activity.FileActivity
Expand Down Expand Up @@ -91,7 +92,7 @@ class ShortcutUtil @Inject constructor(private val mContext: Context) {
thumbnail != null -> IconCompat.createWithAdaptiveBitmap(bitmapToAdaptiveBitmap(thumbnail))

file.isFolder -> {
val isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user)
val isAutoUploadFolder = SyncedFolderObserver.isAutoUploadFolder(file, user)
val isDarkModeActive = syncedFolderProvider.preferences.isDarkModeEnabled
val overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder)
val drawable = MimeTypeUtil.getFolderIcon(isDarkModeActive, overlayIconId, mContext, viewThemeUtils)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.owncloud.android.datamodel

import com.nextcloud.client.account.User
import com.nextcloud.client.database.dao.SyncedFolderDao
import com.owncloud.android.lib.resources.files.model.ServerFileInterface
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch

object SyncedFolderObserver {

@Volatile
private var syncedFoldersMap = mapOf<String, Set<String>>()

private var job: Job? = null
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

fun start(dao: SyncedFolderDao) {
if (job?.isActive == true) return

job = scope.launch {
dao.getAllAsFlow()
.distinctUntilChanged()
.collect { updatedEntities ->
syncedFoldersMap = updatedEntities
.filter { it.remotePath != null && it.account != null }
.groupBy { it.account!! }
.mapValues { (_, entities) ->
entities.map { it.remotePath!!.trimEnd('/') }.toSet()
}
}
}
}

@Suppress("ReturnCount")
fun isAutoUploadFolder(file: ServerFileInterface, user: User): Boolean {
val accountFolders = syncedFoldersMap[user.accountName] ?: return false
val normalizedRemotePath = file.remotePath.trimEnd('/')
if (normalizedRemotePath.isEmpty()) return false
return accountFolders.any { entityPath ->
normalizedRemotePath == entityPath
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import com.owncloud.android.MainApp;
import com.owncloud.android.db.ProviderMeta;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.files.model.ServerFileInterface;

import java.io.File;
import java.util.ArrayList;
Expand Down Expand Up @@ -61,6 +60,7 @@ public SyncedFolderProvider(ContentResolver contentResolver, AppPreferences pref
mContentResolver = contentResolver;
this.preferences = preferences;
this.clock = clock;
SyncedFolderObserver.INSTANCE.start(dao);
}

/**
Expand All @@ -84,23 +84,19 @@ public long storeSyncedFolder(SyncedFolder syncedFolder) {
}
}

public static boolean isAutoUploadFolder(SyncedFolderProvider syncedFolderProvider, ServerFileInterface file, User user) {
return syncedFolderProvider != null && syncedFolderProvider.findByRemotePathAndAccount(file.getRemotePath(), user);
}

public int countEnabledSyncedFolders() {
int count = 0;
Cursor cursor = mContentResolver.query(
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
null,
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED + " = ?",
new String[]{"1"},
null
);
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
null,
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED + " = ?",
new String[]{"1"},
null
);

if (cursor != null) {
count = cursor.getCount();
cursor.close();
count = cursor.getCount();
cursor.close();
}

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

int result = 0;
Cursor cursor = mContentResolver.query(
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
null,
ProviderMeta.ProviderTableMeta._ID + "=?",
new String[]{String.valueOf(id)},
null
);
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
null,
ProviderMeta.ProviderTableMeta._ID + "=?",
new String[]{String.valueOf(id)},
null
);

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

Expand Down Expand Up @@ -226,10 +222,10 @@ public SyncedFolder getSyncedFolderByID(Long syncedFolderID) {
*/
public int deleteSyncFoldersForAccount(User user) {
return mContentResolver.delete(
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " = ?",
new String[]{String.valueOf(user.getAccountName())}
);
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " = ?",
new String[]{String.valueOf(user.getAccountName())}
);
}

/**
Expand Down Expand Up @@ -284,10 +280,10 @@ public void updateAutoUploadPaths(Context context) {
*/
public int deleteSyncedFoldersNotInList(List<Long> ids) {
int result = mContentResolver.delete(
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
ProviderMeta.ProviderTableMeta._ID + " NOT IN (?)",
new String[]{String.valueOf(ids)}
);
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
ProviderMeta.ProviderTableMeta._ID + " NOT IN (?)",
new String[]{String.valueOf(ids)}
);

if(result > 0) {
preferences.setLegacyClean(true);
Expand All @@ -301,10 +297,10 @@ public int deleteSyncedFoldersNotInList(List<Long> ids) {
*/
public int deleteSyncedFolder(long id) {
return mContentResolver.delete(
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
ProviderMeta.ProviderTableMeta._ID + " = ?",
new String[]{String.valueOf(id)}
);
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
ProviderMeta.ProviderTableMeta._ID + " = ?",
new String[]{String.valueOf(id)}
);
}

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

return mContentResolver.update(
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
cv,
ProviderMeta.ProviderTableMeta._ID + "=?",
new String[]{String.valueOf(syncedFolder.getId())}
);
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
cv,
ProviderMeta.ProviderTableMeta._ID + "=?",
new String[]{String.valueOf(syncedFolder.getId())}
);
}

/**
Expand All @@ -341,33 +337,33 @@ private SyncedFolder createSyncedFolderFromCursor(Cursor cursor) {
if (cursor != null) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ProviderMeta.ProviderTableMeta._ID));
String localPath = cursor.getString(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH));
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH));
String remotePath = cursor.getString(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH));
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH));
boolean wifiOnly = cursor.getInt(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY)) == 1;
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY)) == 1;
boolean chargingOnly = cursor.getInt(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1;
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1;
boolean existing = cursor.getInt(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_EXISTING)) == 1;
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_EXISTING)) == 1;
boolean subfolderByDate = cursor.getInt(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1;
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1;
String accountName = cursor.getString(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT));
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT));
int uploadAction = cursor.getInt(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION));
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION));
int nameCollisionPolicy = cursor.getInt(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_NAME_COLLISION_POLICY));
boolean enabled = cursor.getInt(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1;
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1;
long enabledTimestampMs = cursor.getLong(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS));
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS));
MediaFolderType type = MediaFolderType.getById(cursor.getInt(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE)));
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE)));
boolean hidden = cursor.getInt(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_HIDDEN)) == 1;
SubFolderRule subFolderRule = SubFolderRule.values()[cursor.getInt(
cursor.getColumnIndexOrThrow(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_RULE))];
cursor.getColumnIndexOrThrow(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_RULE))];
boolean excludeHidden = cursor.getInt(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_EXCLUDE_HIDDEN)) == 1;
long lastScanTimestampMs = cursor.getLong(cursor.getColumnIndexOrThrow(
Expand Down Expand Up @@ -423,48 +419,4 @@ private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFol
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LAST_SCAN_TIMESTAMP_MS, syncedFolder.getLastScanTimestampMs());
return cv;
}

/**
* method to check if sync folder for the remote path exist in table or not
*
* @param remotePath to be check
* @param user for which we are looking
* @return <code>true</code> if exist, <code>false</code> otherwise
*/
public boolean findByRemotePathAndAccount(String remotePath, User user) {
if (user == null) {
return false;
}

boolean result = false;

//if path ends with / then remove the last / to work the query right way
//because the sub folders of synced folders will not have the slash at the end
if (remotePath.endsWith("/")) {
remotePath = remotePath.substring(0, remotePath.length() - 1);
}

Cursor cursor = mContentResolver.query(
ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
null,
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH + " LIKE ? AND " +
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " =? ",
new String[]{"%" + remotePath + "%", user.getAccountName()},
null);

if (cursor != null && cursor.getCount() >= 1) {
result = true;
} else {
if (cursor == null) {
Log_OC.e(TAG, "Sync folder db cursor for remote path = " + remotePath + " in NULL.");
}
}

if (cursor != null) {
cursor.close();
}

return result;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.owncloud.android.R;
import com.owncloud.android.databinding.RichdocumentsWebviewBinding;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.SyncedFolderObserver;
import com.owncloud.android.datamodel.SyncedFolderProvider;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
import com.owncloud.android.ui.asynctasks.TextEditorLoadUrlTask;
Expand Down Expand Up @@ -251,7 +252,7 @@ protected void setThumbnailView(final User user) {
// Todo minimize: only icon by mimetype
OCFile file = getFile();
if (file.isFolder()) {
boolean isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user);
boolean isAutoUploadFolder = SyncedFolderObserver.INSTANCE.isAutoUploadFolder(file, user);

Integer overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder);
LayerDrawable drawable = MimeTypeUtil.getFolderIcon(preferences.isDarkModeEnabled(), overlayIconId, this, viewThemeUtils);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.owncloud.android.R;
import com.owncloud.android.databinding.ShareActivityBinding;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.SyncedFolderObserver;
import com.owncloud.android.datamodel.SyncedFolderProvider;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
import com.owncloud.android.lib.common.operations.RemoteOperation;
Expand Down Expand Up @@ -63,14 +64,14 @@ protected void onCreate(Bundle savedInstanceState) {

OCFile file = getFile();
Optional<User> optionalUser = getUser();
if (!optionalUser.isPresent()) {
if (optionalUser.isEmpty()) {
finish();
return;
}

// Icon
if (file.isFolder()) {
boolean isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, optionalUser.get());
boolean isAutoUploadFolder = SyncedFolderObserver.INSTANCE.isAutoUploadFolder(file, optionalUser.get());

Integer overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder);
LayerDrawable drawable = MimeTypeUtil.getFolderIcon(preferences.isDarkModeEnabled(), overlayIconId, this, viewThemeUtils);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.nextcloud.client.account.User
import com.owncloud.android.databinding.UploaderListItemLayoutBinding
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.SyncedFolderObserver
import com.owncloud.android.datamodel.SyncedFolderProvider
import com.owncloud.android.datamodel.ThumbnailsCacheManager
import com.owncloud.android.datamodel.ThumbnailsCacheManager.AsyncThumbnailDrawable
Expand Down Expand Up @@ -113,7 +114,7 @@ class ReceiveExternalFilesAdapter(
}

private fun setupThumbnailForFolder(thumbnailImageView: ImageView, file: OCFile) {
val isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user)
val isAutoUploadFolder = SyncedFolderObserver.isAutoUploadFolder(file, user)
val isDarkModeActive = syncedFolderProvider.preferences.isDarkModeEnabled
val overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder)
val icon = MimeTypeUtil.getFolderIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils)
Expand Down
Loading
Loading