Compare commits
14 Commits
04cca36381
...
a7d1beef56
Author | SHA1 | Date | |
---|---|---|---|
|
a7d1beef56 | ||
|
df32926a23 | ||
|
7d08bb527b | ||
|
8ee9a46978 | ||
|
be5c47e5ab | ||
|
bba5d789b6 | ||
|
4d732c0c88 | ||
|
77ae16fab8 | ||
|
bf2479f771 | ||
|
6315f1d621 | ||
|
50eb848926 | ||
|
fa83d67bfa | ||
|
05bb8a21a2 | ||
|
e6f38a9704 |
@ -1533,6 +1533,46 @@ public class FileDataStorageManager {
|
||||
return shares;
|
||||
}
|
||||
|
||||
private ContentValues createContentValueForRemoteFile(RemoteFile remoteFile) {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
|
||||
OCFile ocFile = getFileByDecryptedRemotePath(remoteFile.getRemotePath());
|
||||
if (ocFile != null) {
|
||||
contentValues.put(ProviderTableMeta.OCSHARES_FILE_SOURCE, ocFile.getFileId());
|
||||
}
|
||||
|
||||
contentValues.put(ProviderTableMeta.OCSHARES_PATH, remoteFile.getRemotePath());
|
||||
boolean isDirectory = MimeTypeUtil.isFolder(remoteFile.getMimeType());
|
||||
contentValues.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, isDirectory);
|
||||
contentValues.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, user.getAccountName());
|
||||
contentValues.put(ProviderTableMeta.OCSHARES_USER_ID, remoteFile.getOwnerId());
|
||||
contentValues.put(ProviderTableMeta.OCSHARES_NOTE, remoteFile.getNote());
|
||||
|
||||
if (remoteFile.getSharees().length > 0) {
|
||||
final var sharee = remoteFile.getSharees()[0];
|
||||
contentValues.put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, sharee.getDisplayName());
|
||||
contentValues.put(ProviderTableMeta.OCSHARES_SHARE_WITH, sharee.getUserId());
|
||||
|
||||
ShareType shareType = sharee.getShareType();
|
||||
if (shareType != null) {
|
||||
contentValues.put(ProviderTableMeta.OCSHARES_SHARE_TYPE, shareType.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (!remoteFile.getFileDownloadLimit().isEmpty()) {
|
||||
FileDownloadLimit downloadLimit = remoteFile.getFileDownloadLimit().get(0);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return contentValues;
|
||||
}
|
||||
|
||||
private ContentValues createContentValueForShare(OCShare share) {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(ProviderTableMeta.OCSHARES_FILE_SOURCE, share.getFileSource());
|
||||
@ -1606,25 +1646,6 @@ public class FileDataStorageManager {
|
||||
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()};
|
||||
|
||||
if (getContentResolver() != null) {
|
||||
getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs);
|
||||
|
||||
} else {
|
||||
try {
|
||||
getContentProviderClient().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs);
|
||||
} catch (RemoteException e) {
|
||||
Log_OC.e(TAG, "Exception in resetShareFlagsInAllFiles" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resetShareFlagsInFolder(OCFile folder) {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, Boolean.FALSE);
|
||||
@ -1741,6 +1762,35 @@ public class FileDataStorageManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void saveSharesFromRemoteFile(List<RemoteFile> shares) {
|
||||
if (shares == null || shares.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ArrayList<ContentProviderOperation> operations = prepareInsertSharesFromRemoteFile(shares);
|
||||
|
||||
if (operations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyBatch(operations);
|
||||
}
|
||||
|
||||
private ArrayList<ContentProviderOperation> prepareInsertSharesFromRemoteFile(Iterable<RemoteFile> remoteFiles) {
|
||||
final ArrayList<ContentProviderOperation> operations = new ArrayList<>();
|
||||
|
||||
ContentValues contentValues;
|
||||
for (RemoteFile remoteFile : remoteFiles) {
|
||||
contentValues = createContentValueForRemoteFile(remoteFile);
|
||||
operations.add(ContentProviderOperation
|
||||
.newInsert(ProviderTableMeta.CONTENT_URI_SHARE)
|
||||
.withValues(contentValues)
|
||||
.build());
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
public void saveSharesDB(List<OCShare> shares) {
|
||||
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
|
||||
|
||||
@ -1757,20 +1807,26 @@ public class FileDataStorageManager {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1828,8 +1884,7 @@ public class FileDataStorageManager {
|
||||
* @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
|
||||
|
@ -963,20 +963,12 @@ public final class ThumbnailsCacheManager {
|
||||
thumbnail = addThumbnailToCache(imageKey, bitmap, file.getPath(), px, px);
|
||||
}
|
||||
} else if (Type.VIDEO == type) {
|
||||
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
||||
try {
|
||||
try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) {
|
||||
retriever.setDataSource(file.getAbsolutePath());
|
||||
thumbnail = retriever.getFrameAtTime(-1);
|
||||
} catch (Exception ex) {
|
||||
// can't create a bitmap
|
||||
Log_OC.w(TAG, "Failed to create bitmap from video " + file.getAbsolutePath());
|
||||
} finally {
|
||||
try {
|
||||
retriever.release();
|
||||
} catch (RuntimeException | IOException ex) {
|
||||
// Ignore failure at this point.
|
||||
Log_OC.w(TAG, "Failed release MediaMetadataRetriever for " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
if (thumbnail != null) {
|
||||
|
@ -265,8 +265,16 @@ public class RefreshFolderOperation extends RemoteOperation {
|
||||
sendLocalBroadcast(EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result);
|
||||
}
|
||||
|
||||
if (result.isSuccess() && !mSyncFullAccount && !mOnlyFileMetadata) {
|
||||
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) {
|
||||
@ -804,15 +812,6 @@ public class RefreshFolderOperation extends RemoteOperation {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -953,15 +953,11 @@ public class UploadFileOperation extends SyncOperation {
|
||||
File temporalFile = null;
|
||||
File originalFile = new File(mOriginalStoragePath);
|
||||
File expectedFile = null;
|
||||
FileLock fileLock = null;
|
||||
FileChannel channel = null;
|
||||
|
||||
long size;
|
||||
|
||||
try {
|
||||
// check conditions
|
||||
result = checkConditions(originalFile);
|
||||
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
@ -983,107 +979,121 @@ public class UploadFileOperation extends SyncOperation {
|
||||
|
||||
// Get the last modification date of the file from the file system
|
||||
long lastModifiedTimestamp = originalFile.lastModified() / 1000;
|
||||
|
||||
final Long creationTimestamp = FileUtil.getCreationTimestamp(originalFile);
|
||||
|
||||
try {
|
||||
channel = new RandomAccessFile(mFile.getStoragePath(), "rw").getChannel();
|
||||
fileLock = channel.tryLock();
|
||||
} catch (FileNotFoundException e) {
|
||||
// this basically means that the file is on SD card
|
||||
// try to copy file to temporary dir if it doesn't exist
|
||||
String temporalPath = FileStorageUtils.getInternalTemporalPath(user.getAccountName(), mContext) +
|
||||
mFile.getRemotePath();
|
||||
mFile.setStoragePath(temporalPath);
|
||||
temporalFile = new File(temporalPath);
|
||||
// Initialize channel and fileLock in try-with-resources
|
||||
try (
|
||||
FileChannel channel = new RandomAccessFile(mFile.getStoragePath(), "rw").getChannel();
|
||||
FileLock fileLock = channel.tryLock()
|
||||
) {
|
||||
if (fileLock == null) {
|
||||
// Handle the case when the file lock cannot be acquired
|
||||
String temporalPath = FileStorageUtils.getInternalTemporalPath(user.getAccountName(), mContext) + mFile.getRemotePath();
|
||||
mFile.setStoragePath(temporalPath);
|
||||
temporalFile = new File(temporalPath);
|
||||
|
||||
Files.deleteIfExists(Paths.get(temporalPath));
|
||||
result = copy(originalFile, temporalFile);
|
||||
Files.deleteIfExists(Paths.get(temporalPath));
|
||||
result = copy(originalFile, temporalFile);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
if (temporalFile.length() == originalFile.length()) {
|
||||
channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
|
||||
fileLock = channel.tryLock();
|
||||
if (result.isSuccess()) {
|
||||
if (temporalFile.length() == originalFile.length()) {
|
||||
// Acquire lock on temporary file
|
||||
try (FileChannel tempChannel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
|
||||
FileLock tempFileLock = tempChannel.tryLock()) {
|
||||
if (tempFileLock != null) {
|
||||
// Use the temporary channel for the upload
|
||||
size = tempChannel.size();
|
||||
updateSize(size);
|
||||
|
||||
// Perform the upload operation
|
||||
if (size > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) {
|
||||
boolean onWifiConnection = connectivityService.getConnectivity().isWifi();
|
||||
mUploadOperation = new ChunkedFileUploadRemoteOperation(
|
||||
mFile.getStoragePath(),
|
||||
mFile.getRemotePath(),
|
||||
mFile.getMimeType(),
|
||||
mFile.getEtagInConflict(),
|
||||
lastModifiedTimestamp,
|
||||
creationTimestamp,
|
||||
onWifiConnection,
|
||||
mDisableRetries
|
||||
);
|
||||
} else {
|
||||
mUploadOperation = new UploadFileRemoteOperation(
|
||||
mFile.getStoragePath(),
|
||||
mFile.getRemotePath(),
|
||||
mFile.getMimeType(),
|
||||
mFile.getEtagInConflict(),
|
||||
lastModifiedTimestamp,
|
||||
creationTimestamp,
|
||||
mDisableRetries
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (result.isSuccess() && mUploadOperation != null) {
|
||||
result = mUploadOperation.execute(client);
|
||||
if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED) {
|
||||
result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
size = channel.size();
|
||||
updateSize(size);
|
||||
|
||||
// Perform the upload operation
|
||||
if (size > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) {
|
||||
boolean onWifiConnection = connectivityService.getConnectivity().isWifi();
|
||||
mUploadOperation = new ChunkedFileUploadRemoteOperation(
|
||||
mFile.getStoragePath(),
|
||||
mFile.getRemotePath(),
|
||||
mFile.getMimeType(),
|
||||
mFile.getEtagInConflict(),
|
||||
lastModifiedTimestamp,
|
||||
creationTimestamp,
|
||||
onWifiConnection,
|
||||
mDisableRetries
|
||||
);
|
||||
} else {
|
||||
result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
|
||||
mUploadOperation = new UploadFileRemoteOperation(
|
||||
mFile.getStoragePath(),
|
||||
mFile.getRemotePath(),
|
||||
mFile.getMimeType(),
|
||||
mFile.getEtagInConflict(),
|
||||
lastModifiedTimestamp,
|
||||
creationTimestamp,
|
||||
mDisableRetries
|
||||
);
|
||||
}
|
||||
|
||||
if (result.isSuccess() && mUploadOperation != null) {
|
||||
result = mUploadOperation.execute(client);
|
||||
if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED) {
|
||||
result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Log_OC.d(TAG, mOriginalStoragePath + " not exists anymore");
|
||||
result = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
|
||||
} catch (OverlappingFileLockException e) {
|
||||
Log_OC.d(TAG, "Overlapping file lock exception");
|
||||
result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
|
||||
} catch (Exception e) {
|
||||
result = new RemoteOperationResult(e);
|
||||
}
|
||||
|
||||
try {
|
||||
size = channel.size();
|
||||
} catch (Exception e1) {
|
||||
size = new File(mFile.getStoragePath()).length();
|
||||
}
|
||||
|
||||
updateSize(size);
|
||||
|
||||
// perform the upload
|
||||
if (size > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) {
|
||||
boolean onWifiConnection = connectivityService.getConnectivity().isWifi();
|
||||
|
||||
mUploadOperation = new ChunkedFileUploadRemoteOperation(mFile.getStoragePath(),
|
||||
mFile.getRemotePath(),
|
||||
mFile.getMimeType(),
|
||||
mFile.getEtagInConflict(),
|
||||
lastModifiedTimestamp,
|
||||
creationTimestamp,
|
||||
onWifiConnection,
|
||||
mDisableRetries);
|
||||
} else {
|
||||
mUploadOperation = new UploadFileRemoteOperation(mFile.getStoragePath(),
|
||||
mFile.getRemotePath(),
|
||||
mFile.getMimeType(),
|
||||
mFile.getEtagInConflict(),
|
||||
lastModifiedTimestamp,
|
||||
creationTimestamp,
|
||||
mDisableRetries);
|
||||
}
|
||||
|
||||
for (OnDatatransferProgressListener mDataTransferListener : mDataTransferListeners) {
|
||||
mUploadOperation.addDataTransferProgressListener(mDataTransferListener);
|
||||
}
|
||||
|
||||
if (mCancellationRequested.get()) {
|
||||
throw new OperationCancelledException();
|
||||
}
|
||||
|
||||
if (result.isSuccess() && mUploadOperation != null) {
|
||||
result = mUploadOperation.execute(client);
|
||||
|
||||
/// move local temporal file or original file to its corresponding
|
||||
// location in the Nextcloud local folder
|
||||
if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED) {
|
||||
result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Log_OC.d(TAG, mOriginalStoragePath + " not exists anymore");
|
||||
result = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
|
||||
} catch (OverlappingFileLockException e) {
|
||||
Log_OC.d(TAG, "Overlapping file lock exception");
|
||||
result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
|
||||
} catch (Exception e) {
|
||||
result = new RemoteOperationResult(e);
|
||||
} finally {
|
||||
// Finalize cleanup
|
||||
mUploadStarted.set(false);
|
||||
|
||||
if (fileLock != null) {
|
||||
try {
|
||||
fileLock.release();
|
||||
} catch (IOException e) {
|
||||
Log_OC.e(TAG, "Failed to unlock file with path " + mOriginalStoragePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (channel != null) {
|
||||
try {
|
||||
channel.close();
|
||||
} catch (IOException e) {
|
||||
Log_OC.w(TAG, "Failed to close file channel");
|
||||
}
|
||||
}
|
||||
|
||||
if (temporalFile != null && !originalFile.equals(temporalFile)) {
|
||||
temporalFile.delete();
|
||||
}
|
||||
@ -1093,6 +1103,8 @@ public class UploadFileOperation extends SyncOperation {
|
||||
}
|
||||
|
||||
logResult(result, mOriginalStoragePath, mRemotePath);
|
||||
} catch (Exception e) {
|
||||
result = new RemoteOperationResult(e);
|
||||
}
|
||||
|
||||
if (result.isSuccess()) {
|
||||
@ -1101,7 +1113,7 @@ public class UploadFileOperation extends SyncOperation {
|
||||
getStorageManager().saveConflict(mFile, mFile.getEtagInConflict());
|
||||
}
|
||||
|
||||
// delete temporal file
|
||||
// Delete temporal file
|
||||
if (temporalFile != null && temporalFile.exists() && !temporalFile.delete()) {
|
||||
Log_OC.e(TAG, "Could not delete temporal file " + temporalFile.getAbsolutePath());
|
||||
}
|
||||
@ -1109,6 +1121,7 @@ public class UploadFileOperation extends SyncOperation {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private void updateSize(long size) {
|
||||
OCUpload ocUpload = uploadsStorageManager.getUploadById(getOCUploadId());
|
||||
if (ocUpload != null) {
|
||||
@ -1532,22 +1545,16 @@ public class UploadFileOperation extends SyncOperation {
|
||||
if (!sourceFile.renameTo(targetFile)) {
|
||||
// try to copy and then delete
|
||||
targetFile.createNewFile();
|
||||
FileChannel inChannel = new FileInputStream(sourceFile).getChannel();
|
||||
FileChannel outChannel = new FileOutputStream(targetFile).getChannel();
|
||||
try {
|
||||
try (
|
||||
FileChannel inChannel = new FileInputStream(sourceFile).getChannel();
|
||||
FileChannel outChannel = new FileOutputStream(targetFile).getChannel()
|
||||
) {
|
||||
inChannel.transferTo(0, inChannel.size(), outChannel);
|
||||
sourceFile.delete();
|
||||
} catch (Exception e) {
|
||||
mFile.setStoragePath(""); // forget the local file
|
||||
// by now, treat this as a success; the file was uploaded
|
||||
// the best option could be show a warning message
|
||||
} finally {
|
||||
if (inChannel != null) {
|
||||
inChannel.close();
|
||||
}
|
||||
if (outChannel != null) {
|
||||
outChannel.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,7 +345,13 @@ public class FileContentProvider extends ContentProvider {
|
||||
|
||||
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:
|
||||
|
@ -117,9 +117,6 @@ public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, Result
|
||||
protected ResultCode doInBackground(Object[] params) {
|
||||
|
||||
ResultCode result = ResultCode.UNKNOWN_ERROR;
|
||||
|
||||
InputStream inputStream = null;
|
||||
FileOutputStream outputStream = null;
|
||||
String fullTempPath = null;
|
||||
Uri currentUri = null;
|
||||
|
||||
@ -137,11 +134,7 @@ public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, Result
|
||||
currentRemotePath = remotePaths[i];
|
||||
|
||||
long lastModified = 0;
|
||||
try (Cursor cursor = leakedContentResolver.query(currentUri,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null)) {
|
||||
try (Cursor cursor = leakedContentResolver.query(currentUri, null, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
// this check prevents a crash when last modification time is not available on certain phones
|
||||
int columnIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED);
|
||||
@ -152,42 +145,37 @@ public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, Result
|
||||
}
|
||||
|
||||
fullTempPath = FileStorageUtils.getTemporalPath(user.getAccountName()) + currentRemotePath;
|
||||
inputStream = leakedContentResolver.openInputStream(currentUri);
|
||||
File cacheFile = new File(fullTempPath);
|
||||
File tempDir = cacheFile.getParentFile();
|
||||
if (!tempDir.exists()) {
|
||||
tempDir.mkdirs();
|
||||
}
|
||||
cacheFile.createNewFile();
|
||||
outputStream = new FileOutputStream(fullTempPath);
|
||||
byte[] buffer = new byte[4096];
|
||||
|
||||
int count;
|
||||
while ((count = inputStream.read(buffer)) > 0) {
|
||||
outputStream.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
if (lastModified != 0) {
|
||||
try {
|
||||
if (!cacheFile.setLastModified(lastModified)) {
|
||||
Log_OC.w(TAG, "Could not change mtime of cacheFile");
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
Log_OC.e(TAG, "Not enough permissions to change mtime of cacheFile", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log_OC.e(TAG, "Could not change mtime of cacheFile, mtime is negativ: "+lastModified, e);
|
||||
try (InputStream inputStream = leakedContentResolver.openInputStream(currentUri);
|
||||
FileOutputStream outputStream = new FileOutputStream(fullTempPath)) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int count;
|
||||
while ((count = inputStream.read(buffer)) > 0) {
|
||||
outputStream.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
if (lastModified != 0) {
|
||||
try {
|
||||
if (!cacheFile.setLastModified(lastModified)) {
|
||||
Log_OC.w(TAG, "Could not change mtime of cacheFile");
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
Log_OC.e(TAG, "Not enough permissions to change mtime of cacheFile", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log_OC.e(TAG, "Could not change mtime of cacheFile, mtime is negativ: " + lastModified, e);
|
||||
}
|
||||
}
|
||||
|
||||
requestUpload(user, fullTempPath, currentRemotePath, behaviour);
|
||||
fullTempPath = null;
|
||||
}
|
||||
|
||||
requestUpload(
|
||||
user,
|
||||
fullTempPath,
|
||||
currentRemotePath,
|
||||
behaviour
|
||||
);
|
||||
fullTempPath = null;
|
||||
}
|
||||
|
||||
result = ResultCode.OK;
|
||||
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
@ -206,7 +194,7 @@ public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, Result
|
||||
|
||||
} catch (Exception e) {
|
||||
Log_OC.e(TAG, "Exception while copying " + currentUri + " to temporary file", e);
|
||||
result = ResultCode.LOCAL_STORAGE_NOT_COPIED;
|
||||
result = ResultCode.LOCAL_STORAGE_NOT_COPIED;
|
||||
|
||||
// clean
|
||||
if (fullTempPath != null) {
|
||||
@ -216,22 +204,6 @@ public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, Result
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (Exception e) {
|
||||
Log_OC.w(TAG, "Ignoring exception of inputStream closure");
|
||||
}
|
||||
}
|
||||
|
||||
if (outputStream != null) {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (Exception e) {
|
||||
Log_OC.w(TAG, "Ignoring exception of outStream closure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2018 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.owncloud.android.ui.decoration;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* DividerItemDecoration based on {@link DividerItemDecoration} adding a 72dp left padding.
|
||||
*/
|
||||
public class SimpleListItemDividerDecoration extends DividerItemDecoration {
|
||||
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
|
||||
|
||||
private final Rect bounds = new Rect();
|
||||
private Drawable divider;
|
||||
private int leftPadding;
|
||||
|
||||
/**
|
||||
* Default divider will be used
|
||||
*/
|
||||
public SimpleListItemDividerDecoration(Context context) {
|
||||
super(context, DividerItemDecoration.VERTICAL);
|
||||
final TypedArray styledAttributes = context.obtainStyledAttributes(ATTRS);
|
||||
divider = styledAttributes.getDrawable(0);
|
||||
leftPadding = Math.round(72 * (context.getResources().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
|
||||
styledAttributes.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
|
||||
canvas.save();
|
||||
final int right;
|
||||
//noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
|
||||
if (parent.getClipToPadding()) {
|
||||
right = parent.getWidth() - parent.getPaddingRight();
|
||||
canvas.clipRect(leftPadding, parent.getPaddingTop(), right,
|
||||
parent.getHeight() - parent.getPaddingBottom());
|
||||
} else {
|
||||
right = parent.getWidth();
|
||||
}
|
||||
|
||||
final int childCount = parent.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final View child = parent.getChildAt(i);
|
||||
parent.getDecoratedBoundsWithMargins(child, bounds);
|
||||
final int bottom = bounds.bottom + Math.round(child.getTranslationY());
|
||||
final int top = bottom - 1;
|
||||
|
||||
if (divider != null) {
|
||||
divider.setBounds(leftPadding, top, right, bottom);
|
||||
divider.draw(canvas);
|
||||
}
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
@ -60,6 +60,8 @@ import com.owncloud.android.ui.adapter.ShareeListAdapter;
|
||||
import com.owncloud.android.ui.adapter.ShareeListAdapterListener;
|
||||
import com.owncloud.android.ui.asynctasks.RetrieveHoverCardAsyncTask;
|
||||
import com.owncloud.android.ui.dialog.SharePasswordDialogFragment;
|
||||
import com.owncloud.android.ui.fragment.share.RemoteShareRepository;
|
||||
import com.owncloud.android.ui.fragment.share.ShareRepository;
|
||||
import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper;
|
||||
import com.owncloud.android.ui.helpers.FileOperationsHelper;
|
||||
import com.owncloud.android.utils.ClipboardUtil;
|
||||
@ -149,6 +151,10 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
|
||||
if (fileActivity == null) {
|
||||
throw new IllegalArgumentException("FileActivity may not be null");
|
||||
}
|
||||
|
||||
fileDataStorageManager = fileActivity.getStorageManager();
|
||||
ShareRepository shareRepository = new RemoteShareRepository(fileActivity.getClientRepository(), fileActivity, fileDataStorageManager);
|
||||
shareRepository.refreshSharesForFolder(file.getRemotePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -164,7 +170,6 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
|
||||
binding = FileDetailsSharingFragmentBinding.inflate(inflater, container, false);
|
||||
|
||||
fileOperationsHelper = fileActivity.getFileOperationsHelper();
|
||||
fileDataStorageManager = fileActivity.getStorageManager();
|
||||
|
||||
AccountManager accountManager = AccountManager.get(requireContext());
|
||||
String userId = accountManager.getUserData(user.toPlatformAccount(),
|
||||
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.owncloud.android.ui.fragment.share
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.nextcloud.repository.ClientRepository
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager
|
||||
import com.owncloud.android.lib.common.utils.Log_OC
|
||||
import com.owncloud.android.operations.GetSharesForFileOperation
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class RemoteShareRepository(
|
||||
private val clientRepository: ClientRepository,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
private val fileDataStorageManager: FileDataStorageManager
|
||||
) : ShareRepository {
|
||||
private val tag = "RemoteShareRepository"
|
||||
private val scope = lifecycleOwner.lifecycleScope
|
||||
|
||||
override fun refreshSharesForFolder(remotePath: String) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val client = clientRepository.getOwncloudClient() ?: return@launch
|
||||
val operation =
|
||||
GetSharesForFileOperation(
|
||||
path = remotePath,
|
||||
reshares = true,
|
||||
subfiles = false,
|
||||
storageManager = fileDataStorageManager
|
||||
)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val result = operation.execute(client)
|
||||
|
||||
Log_OC.i(tag, "Remote path for the refresh shares: $remotePath")
|
||||
|
||||
if (result.isSuccess) {
|
||||
Log_OC.d(tag, "Successfully refreshed shares for the specified remote path.")
|
||||
} else {
|
||||
Log_OC.w(
|
||||
tag,
|
||||
"Failed to refresh shares for the specified remote path. An error occurred during the operation."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.owncloud.android.ui.fragment.share
|
||||
|
||||
interface ShareRepository {
|
||||
fun refreshSharesForFolder(remotePath: String)
|
||||
}
|
@ -113,11 +113,9 @@ public class AndroidCalendar {
|
||||
|
||||
private static boolean missing(ContentResolver resolver, Uri uri) {
|
||||
// Determine if a provider is missing
|
||||
ContentProviderClient provider = resolver.acquireContentProviderClient(uri);
|
||||
if (provider != null) {
|
||||
provider.release();
|
||||
try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) {
|
||||
return provider == null;
|
||||
}
|
||||
return provider == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -10,8 +10,8 @@
|
||||
*/
|
||||
buildscript {
|
||||
ext {
|
||||
androidLibraryVersion ="d862794d794a7e8d8b53da98aa801753e684bf52"
|
||||
androidCommonLibraryVersion = "0.25.0"
|
||||
androidLibraryVersion ="7cb4ea1d22"
|
||||
androidCommonLibraryVersion = "0.26.0"
|
||||
androidPluginVersion = "8.9.2"
|
||||
androidxMediaVersion = "1.5.1"
|
||||
androidxTestVersion = "1.6.1"
|
||||
|
@ -2340,6 +2340,11 @@
|
||||
<sha256 value="e8a6c386e1765d870012fed2221173d4a2e8d2fd5aec732c51afb4a9f4684519" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.core" name="core" version="1.16.0">
|
||||
<artifact name="core-1.16.0.aar">
|
||||
<sha256 value="6bf03d39dbe3744acce227d3b697374c3625aae1025fbec8ad9fd7bd58bce431" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.core" name="core" version="1.2.0">
|
||||
<artifact name="core-1.2.0.aar">
|
||||
<sha256 value="524b8b88ceb6a74a7e44e6b567a135660f211799904cb218bfee5be1166820b2" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
@ -11494,6 +11499,14 @@
|
||||
<sha256 value="b03adb110643513771101c07561b36da33381566a04fe43f0e704a98e14de9cf" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.nextcloud" name="android-library" version="7cb4ea1d22">
|
||||
<artifact name="android-library-7cb4ea1d22.aar">
|
||||
<sha256 value="1cf359d041e88438df84743587eacd2a09997a6d9f1183df8d7e360917cd9c14" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="android-library-7cb4ea1d22.module">
|
||||
<sha256 value="7dab0f91c62dcc0e1e07b026c517a97bc62edf90b5c874bd0243b4fad814170e" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.nextcloud" name="android-library" version="84711877b4703f8df3d638a016419b4aa731db2b">
|
||||
<artifact name="android-library-84711877b4703f8df3d638a016419b4aa731db2b.aar">
|
||||
<sha256 value="d801b368fc7d4d5d2d2852765442ccbf7df21f0a9198d369181637a8142914cc" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
@ -11934,6 +11947,14 @@
|
||||
<sha256 value="4af08b995e36b1105770ae517522faf3891f0d88cc6a3a72b7ae6f8ca3b7d959" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.nextcloud.android-common" name="core" version="0.26.0">
|
||||
<artifact name="core-0.26.0.aar">
|
||||
<sha256 value="933d612d1324b21d2e7e04a890bb2c7afda4ffdbe91b76fdea625976f14ba1fb" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="core-0.26.0.module">
|
||||
<sha256 value="b0194f2d5ce11e87603eb9d8d0f61a38442cf310d6445e02bb7c04b593d04ebd" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.nextcloud.android-common" name="core" version="48ed8e86d9">
|
||||
<artifact name="core-48ed8e86d9.aar">
|
||||
<sha256 value="933d612d1324b21d2e7e04a890bb2c7afda4ffdbe91b76fdea625976f14ba1fb" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
@ -12030,6 +12051,14 @@
|
||||
<sha256 value="426e8c06ea5db02c52c00165d1a21c9d583cdf1757ceaf6cdd6a5802d918296c" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.nextcloud.android-common" name="material-color-utilities" version="0.26.0">
|
||||
<artifact name="material-color-utilities-0.26.0.jar">
|
||||
<sha256 value="64c9e61afb6fd0d4ee03dd394429ab1c393f5b66429e036b618285d1bb7d8160" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="material-color-utilities-0.26.0.module">
|
||||
<sha256 value="6e646b238fad314fcbb72e59b198e2d39183191a7fd2dff273ef3e924cf1b915" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.nextcloud.android-common" name="material-color-utilities" version="48ed8e86d9">
|
||||
<artifact name="material-color-utilities-48ed8e86d9.jar">
|
||||
<sha256 value="d4357ec309eb321f6d281cfef414005c72ebdd492a9df44bee68392f7b150f8d" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
@ -12126,6 +12155,14 @@
|
||||
<sha256 value="28d6614254e780308476341c206cf2fdc7984ef509e8b3039f63be78bfdc2f97" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.nextcloud.android-common" name="ui" version="0.26.0">
|
||||
<artifact name="ui-0.26.0.aar">
|
||||
<sha256 value="6a34b9c6e11d1ad2e6578421f9432997b62ca456e41ffcc0c2cbfaa508eb9f11" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="ui-0.26.0.module">
|
||||
<sha256 value="7aadbf0d9e84d63996bdc9f70fdb36a15e633fccda902627241d418bf8363e25" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.nextcloud.android-common" name="ui" version="48ed8e86d9">
|
||||
<artifact name="ui-48ed8e86d9.aar">
|
||||
<sha256 value="2c6d44febd77a3620a73b803bb577aed086af280276698736a81c26a4af292af" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user