Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a8cdaa7
add createContentValueForRemoteFile
alperozturk96 Jun 11, 2025
4bbf559
add createContentValueForRemoteFile
alperozturk96 Jun 11, 2025
8445793
refreshSharesForFolder in share details
alperozturk96 Jun 11, 2025
8964648
fix wrong usages
alperozturk96 Jun 11, 2025
0b510c4
fix wrong usages
alperozturk96 Jun 11, 2025
ac0b160
use lib
alperozturk96 Jun 12, 2025
a14466f
remove unused func
alperozturk96 Jun 12, 2025
891b96a
fix kt spotless
alperozturk96 Jun 12, 2025
44b2714
implement feedbacks
alperozturk96 Jun 12, 2025
8b96d24
indicate parameters
alperozturk96 Jun 12, 2025
4a70e9e
fix FileDownloadLimit crash
alperozturk96 Jun 13, 2025
a3116ed
setDownloadLimitToContentValues
alperozturk96 Jun 13, 2025
0656dc7
support avatar generation for external shares
alperozturk96 Jun 13, 2025
edf5dcb
fix operation creation from remotefile object
alperozturk96 Jun 13, 2025
bcce01d
fix operation creation from remotefile object
alperozturk96 Jun 13, 2025
609b8f3
fix ss test, pass correct argument
alperozturk96 Jun 13, 2025
f485300
check token NPE
alperozturk96 Jun 13, 2025
b7010ea
listen completions of the remote operation
alperozturk96 Jun 13, 2025
c1c17ab
add shimmering
alperozturk96 Jun 13, 2025
03b38f3
add share_list_item_shimmer.xml
alperozturk96 Jun 13, 2025
4ff2cbb
add blink animation
alperozturk96 Jun 13, 2025
2334dcb
kt spotless fix
alperozturk96 Jun 13, 2025
511f9f0
extract layouts
alperozturk96 Jun 13, 2025
16dc863
fix git conflict
alperozturk96 Jul 1, 2025
d68bb85
fix git conflict
alperozturk96 Jul 18, 2025
d99c1b7
fix kt spotless
alperozturk96 Jul 18, 2025
a8f051a
use correct android lib version
alperozturk96 Aug 1, 2025
d153c07
Rename .java to .kt
alperozturk96 Aug 1, 2025
30b45ed
fetch updated data
alperozturk96 Aug 1, 2025
c7dded7
since startSyncFolderOperation called onResume and startSyncFolderOpe…
alperozturk96 Aug 1, 2025
d09c82c
revert
alperozturk96 Aug 1, 2025
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
71 changes: 71 additions & 0 deletions app/src/main/java/com/nextcloud/model/ShareeEntry.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2025 Alper Ozturk <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.model

import android.content.ContentValues
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta
import com.owncloud.android.lib.resources.files.model.RemoteFile
import com.owncloud.android.lib.resources.shares.ShareType

data class ShareeEntry(
val filePath: String?,
val accountOwner: String,
val fileOwnerId: String?,
val shareWithDisplayName: String?,
val shareWithUserId: String?,
val shareType: Int
) {
companion object {
/**
* Extracts a list of share-related ContentValues from a given RemoteFile.
*
* Each RemoteFile can be shared with multiple users (sharees), and this function converts each
* sharee into a ContentValues object, representing a row for insertion into a database.
*
* @param remoteFile The RemoteFile object containing sharee information.
* @param accountName The name of the user account that owns this RemoteFile.
* @return A list of ContentValues representing each share entry, or null if no sharees are found.
*/
fun getContentValues(remoteFile: RemoteFile, accountName: String): List<ContentValues>? {
if (remoteFile.sharees.isNullOrEmpty()) {
return null
}

val result = arrayListOf<ContentValues>()

for (share in remoteFile.sharees) {
val shareType: ShareType? = share?.shareType
if (shareType == null) {
continue
}

val contentValue = ShareeEntry(
remoteFile.remotePath,
accountName,
remoteFile.ownerId,
share.displayName,
share.userId,
shareType.value
).toContentValues()

result.add(contentValue)
}

return result
}
}

private fun toContentValues(): ContentValues = ContentValues().apply {
put(ProviderTableMeta.OCSHARES_PATH, filePath)
put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, accountOwner)
put(ProviderTableMeta.OCSHARES_USER_ID, fileOwnerId)
put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, shareWithDisplayName)
put(ProviderTableMeta.OCSHARES_SHARE_WITH, shareWithUserId)
put(ProviderTableMeta.OCSHARES_SHARE_TYPE, shareType)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import com.nextcloud.model.OCFileFilterType;
import com.nextcloud.model.OfflineOperationRawType;
import com.nextcloud.model.OfflineOperationType;
import com.nextcloud.model.ShareeEntry;
import com.nextcloud.utils.date.DateFormatPattern;
import com.nextcloud.utils.extensions.DateExtensionsKt;
import com.owncloud.android.MainApp;
Expand Down Expand Up @@ -111,6 +112,7 @@ public class FileDataStorageManager {
public final FileDao fileDao = NextcloudDatabase.getInstance(MainApp.getAppContext()).fileDao();
private final Gson gson = new Gson();
public final OfflineOperationsRepositoryType offlineOperationsRepository;
private final static int DEFAULT_CURSOR_INT_VALUE = -1;

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

FileDownloadLimit downloadLimit = share.getFileDownloadLimit();
if (downloadLimit != null) {
contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT, downloadLimit.getLimit());
contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT, downloadLimit.getCount());
} else {
contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT);
contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT);
}
setDownloadLimitToContentValues(contentValues, downloadLimit);

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

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

FileDownloadLimit downloadLimit = new FileDownloadLimit(token,
getInt(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT),
getInt(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT));
share.setFileDownloadLimit(downloadLimit);
FileDownloadLimit fileDownloadLimit = getDownloadLimitFromCursor(cursor, token);
if (fileDownloadLimit != null) {
share.setFileDownloadLimit(fileDownloadLimit);
}

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

return share;
}

private void resetShareFlagsInAllFiles() {
ContentValues cv = new ContentValues();
cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, Boolean.FALSE);
cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, Boolean.FALSE);
String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?";
String[] whereArgs = new String[]{user.getAccountName()};
private void setDownloadLimitToContentValues(ContentValues contentValues, FileDownloadLimit downloadLimit) {
if (downloadLimit != null) {
contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT, downloadLimit.getLimit());
contentValues.put(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT, downloadLimit.getCount());
return;
}

if (getContentResolver() != null) {
getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs);
contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT);
contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT);
}

} else {
try {
getContentProviderClient().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs);
} catch (RemoteException e) {
Log_OC.e(TAG, "Exception in resetShareFlagsInAllFiles" + e.getMessage(), e);
}
@Nullable
private FileDownloadLimit getDownloadLimitFromCursor(Cursor cursor, String token) {
if (token == null || cursor == null) {
return null;
}

int limit = getIntOrDefault(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT);
int count = getIntOrDefault(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT);
if (limit != DEFAULT_CURSOR_INT_VALUE && count != DEFAULT_CURSOR_INT_VALUE) {
return new FileDownloadLimit(token, limit, count);
}

return null;
}

/**
* Retrieves an integer value from the specified column in the cursor.
* <p>
* If the column does not exist (i.e., {@code cursor.getColumnIndex(columnName)} returns -1),
* this method returns {@code -1} as a default value.
* </p>
*
* @param cursor The Cursor from which to retrieve the value.
* @param columnName The name of the column to retrieve the integer from.
* @return The integer value from the column, or {@code -1} if the column is not found.
*/
private int getIntOrDefault(Cursor cursor, String columnName) {
int index = cursor.getColumnIndex(columnName);
if (index == DEFAULT_CURSOR_INT_VALUE) {
return DEFAULT_CURSOR_INT_VALUE;
}

return cursor.getInt(index);
}

private void resetShareFlagsInFolder(OCFile folder) {
Expand Down Expand Up @@ -1743,6 +1766,67 @@ public void removeShare(OCShare share) {
}
}

public void saveSharesFromRemoteFile(List<RemoteFile> shares) {
if (shares == null || shares.isEmpty()) {
return;
}

// Prepare reset operations
Set<String> uniquePaths = new HashSet<>();
for (RemoteFile share : shares) {
uniquePaths.add(share.getRemotePath());
}

ArrayList<ContentProviderOperation> resetOperations = new ArrayList<>();
for (String path : uniquePaths) {
resetShareFlagInAFile(path);
var removeOps = prepareRemoveSharesInFile(path, new ArrayList<>());
if (!removeOps.isEmpty()) {
resetOperations.addAll(removeOps);
}
}
if (!resetOperations.isEmpty()) {
applyBatch(resetOperations);
}

// Prepare insert operations
ArrayList<ContentProviderOperation> insertOperations = prepareInsertSharesFromRemoteFile(shares);
if (!insertOperations.isEmpty()) {
applyBatch(insertOperations);
}
}

/**
* Prepares a list of ContentProviderOperation insert operations based on share information
* found in the given iterable of RemoteFile objects.
* <p>
* Each RemoteFile may have multiple share entries (sharees), and for each one,
* a corresponding ContentProviderOperation is created for insertion into the shares table.
*
* @param remoteFiles An iterable list of RemoteFile objects containing sharee data.
* @return A list of ContentProviderOperation objects for batch insertion into the content provider.
*/
private ArrayList<ContentProviderOperation> prepareInsertSharesFromRemoteFile(Iterable<RemoteFile> remoteFiles) {
final ArrayList<ContentValues> contentValueList = new ArrayList<>();
for (RemoteFile remoteFile : remoteFiles) {
final var contentValues = ShareeEntry.Companion.getContentValues(remoteFile, user.getAccountName());
if (contentValues == null) {
continue;
}
contentValueList.addAll(contentValues);
}

ArrayList<ContentProviderOperation> operations = new ArrayList<>();
for (ContentValues contentValues : contentValueList) {
operations.add(ContentProviderOperation
.newInsert(ProviderTableMeta.CONTENT_URI_SHARE)
.withValues(contentValues)
.build());
}

return operations;
}

public void saveSharesDB(List<OCShare> shares) {
ArrayList<ContentProviderOperation> operations = new ArrayList<>();

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

if (operations.isEmpty()) {
return;
}

// apply operations in batch
if (operations.size() > 0) {
Log_OC.d(TAG, String.format(Locale.ENGLISH, SENDING_TO_FILECONTENTPROVIDER_MSG, operations.size()));
try {
if (getContentResolver() != null) {
getContentResolver().applyBatch(MainApp.getAuthority(), operations);
Log_OC.d(TAG, String.format(Locale.ENGLISH, SENDING_TO_FILECONTENTPROVIDER_MSG, operations.size()));
applyBatch(operations);
}

} else {
getContentProviderClient().applyBatch(operations);
}
private void applyBatch(ArrayList<ContentProviderOperation> operations) {
try {
if (getContentResolver() != null) {
getContentResolver().applyBatch(MainApp.getAuthority(), operations);

} catch (OperationApplicationException | RemoteException e) {
Log_OC.e(TAG, EXCEPTION_MSG + e.getMessage(), e);
} else {
getContentProviderClient().applyBatch(operations);
}

} catch (OperationApplicationException | RemoteException e) {
Log_OC.e(TAG, EXCEPTION_MSG + e.getMessage(), e);
}
}

Expand Down Expand Up @@ -1830,8 +1920,7 @@ public void saveSharesInFolder(ArrayList<OCShare> shares, OCFile folder) {
* @param operations List of operations
* @return
*/
private ArrayList<ContentProviderOperation> prepareInsertShares(
Iterable<OCShare> shares, ArrayList<ContentProviderOperation> operations) {
private ArrayList<ContentProviderOperation> prepareInsertShares(Iterable<OCShare> shares, ArrayList<ContentProviderOperation> operations) {

ContentValues contentValues;
// prepare operations to insert or update files to save in the given folder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ protected RemoteOperationResult run(OwnCloudClient client) {

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

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

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

if (result.isSuccess() && !mSyncFullAccount && !mOnlyFileMetadata && mLocalFolder != null) {
refreshSharesForFolder(client); // share result is ignored
if (result.isSuccess() && result.getData() != null && !mSyncFullAccount && !mOnlyFileMetadata) {
final var remoteObject = result.getData();
final ArrayList<RemoteFile> remoteFiles = new ArrayList<>();
for (Object object: remoteObject) {
if (object instanceof RemoteFile remoteFile) {
remoteFiles.add(remoteFile);
}
}

fileDataStorageManager.saveSharesFromRemoteFile(remoteFiles);
}

if (!mSyncFullAccount && mLocalFolder != null) {
Expand Down Expand Up @@ -791,16 +799,6 @@ private void startContentSynchronizations(List<SynchronizeFileOperation> filesTo
}
}

/**
* Syncs the Share resources for the files contained in the folder refreshed (children, not deeper descendants).
*
* @param client Handler of a session with an OC server.
*/
private void refreshSharesForFolder(OwnCloudClient client) {
GetSharesForFileOperation operation = new GetSharesForFileOperation(mLocalFolder.getRemotePath(), true, true, fileDataStorageManager);
operation.execute(client);
}

/**
* Sends a message to any application component interested in the progress of the synchronization.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,13 @@ private Uri insert(SupportSQLiteDatabase db, Uri uri, ContentValues values) {

private void updateFilesTableAccordingToShareInsertion(SupportSQLiteDatabase db, ContentValues newShare) {
ContentValues fileValues = new ContentValues();
ShareType newShareType = ShareType.fromValue(newShare.getAsInteger(ProviderTableMeta.OCSHARES_SHARE_TYPE));
Integer shareTypeValue = newShare.getAsInteger(ProviderTableMeta.OCSHARES_SHARE_TYPE);
if (shareTypeValue == null) {
Log_OC.w(TAG, "Share type is null. Skipping file update.");
return;
}

ShareType newShareType = ShareType.fromValue(shareTypeValue);

switch (newShareType) {
case PUBLIC_LINK:
Expand Down
Loading
Loading