Skip to content

Commit cdb2893

Browse files
Merge pull request #14989 from nextcloud/do-not-fetch-shares-during-folder-refresh
Do not fetch shares during folder refresh
2 parents b305ebd + d09c82c commit cdb2893

22 files changed

Lines changed: 836 additions & 405 deletions
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.model
9+
10+
import android.content.ContentValues
11+
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta
12+
import com.owncloud.android.lib.resources.files.model.RemoteFile
13+
import com.owncloud.android.lib.resources.shares.ShareType
14+
15+
data class ShareeEntry(
16+
val filePath: String?,
17+
val accountOwner: String,
18+
val fileOwnerId: String?,
19+
val shareWithDisplayName: String?,
20+
val shareWithUserId: String?,
21+
val shareType: Int
22+
) {
23+
companion object {
24+
/**
25+
* Extracts a list of share-related ContentValues from a given RemoteFile.
26+
*
27+
* Each RemoteFile can be shared with multiple users (sharees), and this function converts each
28+
* sharee into a ContentValues object, representing a row for insertion into a database.
29+
*
30+
* @param remoteFile The RemoteFile object containing sharee information.
31+
* @param accountName The name of the user account that owns this RemoteFile.
32+
* @return A list of ContentValues representing each share entry, or null if no sharees are found.
33+
*/
34+
fun getContentValues(remoteFile: RemoteFile, accountName: String): List<ContentValues>? {
35+
if (remoteFile.sharees.isNullOrEmpty()) {
36+
return null
37+
}
38+
39+
val result = arrayListOf<ContentValues>()
40+
41+
for (share in remoteFile.sharees) {
42+
val shareType: ShareType? = share?.shareType
43+
if (shareType == null) {
44+
continue
45+
}
46+
47+
val contentValue = ShareeEntry(
48+
remoteFile.remotePath,
49+
accountName,
50+
remoteFile.ownerId,
51+
share.displayName,
52+
share.userId,
53+
shareType.value
54+
).toContentValues()
55+
56+
result.add(contentValue)
57+
}
58+
59+
return result
60+
}
61+
}
62+
63+
private fun toContentValues(): ContentValues = ContentValues().apply {
64+
put(ProviderTableMeta.OCSHARES_PATH, filePath)
65+
put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, accountOwner)
66+
put(ProviderTableMeta.OCSHARES_USER_ID, fileOwnerId)
67+
put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, shareWithDisplayName)
68+
put(ProviderTableMeta.OCSHARES_SHARE_WITH, shareWithUserId)
69+
put(ProviderTableMeta.OCSHARES_SHARE_TYPE, shareType)
70+
}
71+
}

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

Lines changed: 126 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import com.nextcloud.model.OCFileFilterType;
4545
import com.nextcloud.model.OfflineOperationRawType;
4646
import com.nextcloud.model.OfflineOperationType;
47+
import com.nextcloud.model.ShareeEntry;
4748
import com.nextcloud.utils.date.DateFormatPattern;
4849
import com.nextcloud.utils.extensions.DateExtensionsKt;
4950
import com.owncloud.android.MainApp;
@@ -111,6 +112,7 @@ public class FileDataStorageManager {
111112
public final FileDao fileDao = NextcloudDatabase.getInstance(MainApp.getAppContext()).fileDao();
112113
private final Gson gson = new Gson();
113114
public final OfflineOperationsRepositoryType offlineOperationsRepository;
115+
private final static int DEFAULT_CURSOR_INT_VALUE = -1;
114116

115117
public FileDataStorageManager(User user, ContentResolver contentResolver) {
116118
this.contentProviderClient = null;
@@ -1563,13 +1565,7 @@ private ContentValues createContentValueForShare(OCShare share) {
15631565
contentValues.put(ProviderTableMeta.OCSHARES_SHARE_LABEL, share.getLabel());
15641566

15651567
FileDownloadLimit downloadLimit = share.getFileDownloadLimit();
1566-
if (downloadLimit != null) {
1567-
contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT, downloadLimit.getLimit());
1568-
contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT, downloadLimit.getCount());
1569-
} else {
1570-
contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT);
1571-
contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT);
1572-
}
1568+
setDownloadLimitToContentValues(contentValues, downloadLimit);
15731569

15741570
contentValues.put(ProviderTableMeta.OCSHARES_ATTRIBUTES, share.getAttributes());
15751571

@@ -1598,33 +1594,60 @@ private OCShare createShareInstance(Cursor cursor) {
15981594
share.setShareLink(getString(cursor, ProviderTableMeta.OCSHARES_SHARE_LINK));
15991595
share.setLabel(getString(cursor, ProviderTableMeta.OCSHARES_SHARE_LABEL));
16001596

1601-
FileDownloadLimit downloadLimit = new FileDownloadLimit(token,
1602-
getInt(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT),
1603-
getInt(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT));
1604-
share.setFileDownloadLimit(downloadLimit);
1597+
FileDownloadLimit fileDownloadLimit = getDownloadLimitFromCursor(cursor, token);
1598+
if (fileDownloadLimit != null) {
1599+
share.setFileDownloadLimit(fileDownloadLimit);
1600+
}
16051601

16061602
share.setAttributes(getString(cursor, ProviderTableMeta.OCSHARES_ATTRIBUTES));
16071603

16081604
return share;
16091605
}
16101606

1611-
private void resetShareFlagsInAllFiles() {
1612-
ContentValues cv = new ContentValues();
1613-
cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, Boolean.FALSE);
1614-
cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, Boolean.FALSE);
1615-
String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?";
1616-
String[] whereArgs = new String[]{user.getAccountName()};
1607+
private void setDownloadLimitToContentValues(ContentValues contentValues, FileDownloadLimit downloadLimit) {
1608+
if (downloadLimit != null) {
1609+
contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT, downloadLimit.getLimit());
1610+
contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT, downloadLimit.getCount());
1611+
return;
1612+
}
16171613

1618-
if (getContentResolver() != null) {
1619-
getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs);
1614+
contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT);
1615+
contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT);
1616+
}
16201617

1621-
} else {
1622-
try {
1623-
getContentProviderClient().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs);
1624-
} catch (RemoteException e) {
1625-
Log_OC.e(TAG, "Exception in resetShareFlagsInAllFiles" + e.getMessage(), e);
1626-
}
1618+
@Nullable
1619+
private FileDownloadLimit getDownloadLimitFromCursor(Cursor cursor, String token) {
1620+
if (token == null || cursor == null) {
1621+
return null;
16271622
}
1623+
1624+
int limit = getIntOrDefault(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT);
1625+
int count = getIntOrDefault(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT);
1626+
if (limit != DEFAULT_CURSOR_INT_VALUE && count != DEFAULT_CURSOR_INT_VALUE) {
1627+
return new FileDownloadLimit(token, limit, count);
1628+
}
1629+
1630+
return null;
1631+
}
1632+
1633+
/**
1634+
* Retrieves an integer value from the specified column in the cursor.
1635+
* <p>
1636+
* If the column does not exist (i.e., {@code cursor.getColumnIndex(columnName)} returns -1),
1637+
* this method returns {@code -1} as a default value.
1638+
* </p>
1639+
*
1640+
* @param cursor The Cursor from which to retrieve the value.
1641+
* @param columnName The name of the column to retrieve the integer from.
1642+
* @return The integer value from the column, or {@code -1} if the column is not found.
1643+
*/
1644+
private int getIntOrDefault(Cursor cursor, String columnName) {
1645+
int index = cursor.getColumnIndex(columnName);
1646+
if (index == DEFAULT_CURSOR_INT_VALUE) {
1647+
return DEFAULT_CURSOR_INT_VALUE;
1648+
}
1649+
1650+
return cursor.getInt(index);
16281651
}
16291652

16301653
private void resetShareFlagsInFolder(OCFile folder) {
@@ -1743,6 +1766,67 @@ public void removeShare(OCShare share) {
17431766
}
17441767
}
17451768

1769+
public void saveSharesFromRemoteFile(List<RemoteFile> shares) {
1770+
if (shares == null || shares.isEmpty()) {
1771+
return;
1772+
}
1773+
1774+
// Prepare reset operations
1775+
Set<String> uniquePaths = new HashSet<>();
1776+
for (RemoteFile share : shares) {
1777+
uniquePaths.add(share.getRemotePath());
1778+
}
1779+
1780+
ArrayList<ContentProviderOperation> resetOperations = new ArrayList<>();
1781+
for (String path : uniquePaths) {
1782+
resetShareFlagInAFile(path);
1783+
var removeOps = prepareRemoveSharesInFile(path, new ArrayList<>());
1784+
if (!removeOps.isEmpty()) {
1785+
resetOperations.addAll(removeOps);
1786+
}
1787+
}
1788+
if (!resetOperations.isEmpty()) {
1789+
applyBatch(resetOperations);
1790+
}
1791+
1792+
// Prepare insert operations
1793+
ArrayList<ContentProviderOperation> insertOperations = prepareInsertSharesFromRemoteFile(shares);
1794+
if (!insertOperations.isEmpty()) {
1795+
applyBatch(insertOperations);
1796+
}
1797+
}
1798+
1799+
/**
1800+
* Prepares a list of ContentProviderOperation insert operations based on share information
1801+
* found in the given iterable of RemoteFile objects.
1802+
* <p>
1803+
* Each RemoteFile may have multiple share entries (sharees), and for each one,
1804+
* a corresponding ContentProviderOperation is created for insertion into the shares table.
1805+
*
1806+
* @param remoteFiles An iterable list of RemoteFile objects containing sharee data.
1807+
* @return A list of ContentProviderOperation objects for batch insertion into the content provider.
1808+
*/
1809+
private ArrayList<ContentProviderOperation> prepareInsertSharesFromRemoteFile(Iterable<RemoteFile> remoteFiles) {
1810+
final ArrayList<ContentValues> contentValueList = new ArrayList<>();
1811+
for (RemoteFile remoteFile : remoteFiles) {
1812+
final var contentValues = ShareeEntry.Companion.getContentValues(remoteFile, user.getAccountName());
1813+
if (contentValues == null) {
1814+
continue;
1815+
}
1816+
contentValueList.addAll(contentValues);
1817+
}
1818+
1819+
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
1820+
for (ContentValues contentValues : contentValueList) {
1821+
operations.add(ContentProviderOperation
1822+
.newInsert(ProviderTableMeta.CONTENT_URI_SHARE)
1823+
.withValues(contentValues)
1824+
.build());
1825+
}
1826+
1827+
return operations;
1828+
}
1829+
17461830
public void saveSharesDB(List<OCShare> shares) {
17471831
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
17481832

@@ -1759,20 +1843,26 @@ public void saveSharesDB(List<OCShare> shares) {
17591843
// Add operations to insert shares
17601844
operations = prepareInsertShares(shares, operations);
17611845

1846+
if (operations.isEmpty()) {
1847+
return;
1848+
}
1849+
17621850
// apply operations in batch
1763-
if (operations.size() > 0) {
1764-
Log_OC.d(TAG, String.format(Locale.ENGLISH, SENDING_TO_FILECONTENTPROVIDER_MSG, operations.size()));
1765-
try {
1766-
if (getContentResolver() != null) {
1767-
getContentResolver().applyBatch(MainApp.getAuthority(), operations);
1851+
Log_OC.d(TAG, String.format(Locale.ENGLISH, SENDING_TO_FILECONTENTPROVIDER_MSG, operations.size()));
1852+
applyBatch(operations);
1853+
}
17681854

1769-
} else {
1770-
getContentProviderClient().applyBatch(operations);
1771-
}
1855+
private void applyBatch(ArrayList<ContentProviderOperation> operations) {
1856+
try {
1857+
if (getContentResolver() != null) {
1858+
getContentResolver().applyBatch(MainApp.getAuthority(), operations);
17721859

1773-
} catch (OperationApplicationException | RemoteException e) {
1774-
Log_OC.e(TAG, EXCEPTION_MSG + e.getMessage(), e);
1860+
} else {
1861+
getContentProviderClient().applyBatch(operations);
17751862
}
1863+
1864+
} catch (OperationApplicationException | RemoteException e) {
1865+
Log_OC.e(TAG, EXCEPTION_MSG + e.getMessage(), e);
17761866
}
17771867
}
17781868

@@ -1830,8 +1920,7 @@ public void saveSharesInFolder(ArrayList<OCShare> shares, OCFile folder) {
18301920
* @param operations List of operations
18311921
* @return
18321922
*/
1833-
private ArrayList<ContentProviderOperation> prepareInsertShares(
1834-
Iterable<OCShare> shares, ArrayList<ContentProviderOperation> operations) {
1923+
private ArrayList<ContentProviderOperation> prepareInsertShares(Iterable<OCShare> shares, ArrayList<ContentProviderOperation> operations) {
18351924

18361925
ContentValues contentValues;
18371926
// prepare operations to insert or update files to save in the given folder

app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ protected RemoteOperationResult run(OwnCloudClient client) {
233233

234234
if (mLocalFolder == null) {
235235
Log_OC.e(TAG, "Local folder is null, cannot run refresh folder operation");
236-
return new RemoteOperationResult(ResultCode.FILE_NOT_FOUND);
236+
return new RemoteOperationResult<>(ResultCode.FILE_NOT_FOUND);
237237
}
238238

239239
if (OCFile.ROOT_PATH.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount && !mOnlyFileMetadata) {
@@ -263,16 +263,24 @@ protected RemoteOperationResult run(OwnCloudClient client) {
263263
fileDataStorageManager.saveFile(mLocalFolder);
264264
} else {
265265
Log_OC.e(TAG, "Local folder is null, cannot set last sync date nor save file");
266-
result = new RemoteOperationResult(ResultCode.FILE_NOT_FOUND);
266+
result = new RemoteOperationResult<>(ResultCode.FILE_NOT_FOUND);
267267
}
268268
}
269269

270270
if (!mSyncFullAccount && mRemoteFolderChanged && mLocalFolder != null) {
271271
sendLocalBroadcast(EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result);
272272
}
273273

274-
if (result.isSuccess() && !mSyncFullAccount && !mOnlyFileMetadata && mLocalFolder != null) {
275-
refreshSharesForFolder(client); // share result is ignored
274+
if (result.isSuccess() && result.getData() != null && !mSyncFullAccount && !mOnlyFileMetadata) {
275+
final var remoteObject = result.getData();
276+
final ArrayList<RemoteFile> remoteFiles = new ArrayList<>();
277+
for (Object object: remoteObject) {
278+
if (object instanceof RemoteFile remoteFile) {
279+
remoteFiles.add(remoteFile);
280+
}
281+
}
282+
283+
fileDataStorageManager.saveSharesFromRemoteFile(remoteFiles);
276284
}
277285

278286
if (!mSyncFullAccount && mLocalFolder != null) {
@@ -791,16 +799,6 @@ private void startContentSynchronizations(List<SynchronizeFileOperation> filesTo
791799
}
792800
}
793801

794-
/**
795-
* Syncs the Share resources for the files contained in the folder refreshed (children, not deeper descendants).
796-
*
797-
* @param client Handler of a session with an OC server.
798-
*/
799-
private void refreshSharesForFolder(OwnCloudClient client) {
800-
GetSharesForFileOperation operation = new GetSharesForFileOperation(mLocalFolder.getRemotePath(), true, true, fileDataStorageManager);
801-
operation.execute(client);
802-
}
803-
804802
/**
805803
* Sends a message to any application component interested in the progress of the synchronization.
806804
*

app/src/main/java/com/owncloud/android/providers/FileContentProvider.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,13 @@ private Uri insert(SupportSQLiteDatabase db, Uri uri, ContentValues values) {
345345

346346
private void updateFilesTableAccordingToShareInsertion(SupportSQLiteDatabase db, ContentValues newShare) {
347347
ContentValues fileValues = new ContentValues();
348-
ShareType newShareType = ShareType.fromValue(newShare.getAsInteger(ProviderTableMeta.OCSHARES_SHARE_TYPE));
348+
Integer shareTypeValue = newShare.getAsInteger(ProviderTableMeta.OCSHARES_SHARE_TYPE);
349+
if (shareTypeValue == null) {
350+
Log_OC.w(TAG, "Share type is null. Skipping file update.");
351+
return;
352+
}
353+
354+
ShareType newShareType = ShareType.fromValue(shareTypeValue);
349355

350356
switch (newShareType) {
351357
case PUBLIC_LINK:

0 commit comments

Comments
 (0)