Skip to content
Open
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 @@ -171,4 +171,7 @@ interface FileDao {

@Query("DELETE FROM filelist WHERE file_owner = :fileOwner AND path = :remotePath")
fun deleteFileByRemotePath(fileOwner: String, remotePath: String): Int

@Update
fun updateAll(entities: List<FileEntity>)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@

package com.nextcloud.utils.extensions

import com.nextcloud.client.database.dao.FileDao
import com.nextcloud.client.database.entity.toOCCapability
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.shares.OCShare
import com.owncloud.android.lib.resources.status.OCCapability
import com.owncloud.android.utils.FileStorageUtils
import com.owncloud.android.utils.MimeTypeUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File

suspend fun FileDataStorageManager.saveShares(shares: List<OCShare>, accountName: String) {
withContext(Dispatchers.IO) {
Expand Down Expand Up @@ -58,3 +63,132 @@ fun FileDataStorageManager.getNonEncryptedSubfolders(id: Long, accountName: Stri

suspend fun FileDataStorageManager.getCapabilitiesByAccountName(accountName: String): OCCapability =
capabilityDao.getByAccountName(accountName).toOCCapability()

@Suppress("ReturnCount")
fun FileDataStorageManager.moveFiles(ocFile: OCFile?, targetPath: String, targetParentPath: String) {
Log_OC.d(
FileDataStorageManager.TAG,
(
"moveLocalFile ==> ocFile: " +
(ocFile?.remotePath) +
" targetPath: " +
targetPath +
" targetParentPath: " +
targetParentPath
)
)

if (ocFile == null) {
Log_OC.e(FileDataStorageManager.TAG, "moveLocalFile: file is null, skipping")
return
}

if (!ocFile.fileExists()) {
Log_OC.e(FileDataStorageManager.TAG, "moveLocalFile: file does not exist, skipping")
return
}

if (OCFile.ROOT_PATH == ocFile.fileName) {
Log_OC.w(FileDataStorageManager.TAG, "moveLocalFile: cannot move root path")
return
}

if (ocFile.remotePath == targetPath) {
Log_OC.w(FileDataStorageManager.TAG, "moveLocalFile: source and target paths are identical, skipping")
return
}

val targetParent = getFileByPath(targetParentPath)
if (targetParent == null) {
Log_OC.e(FileDataStorageManager.TAG, "moveLocalFile: target parent folder not found: $targetParentPath")
return
}

if (!targetParent.isFolder) {
Log_OC.e(FileDataStorageManager.TAG, "moveLocalFile: target parent is not a folder: $targetParentPath")
Comment thread
alperozturk96 marked this conversation as resolved.
return
}

val oldPath: String = ocFile.remotePath
val accountName = user.accountName
val defaultSavePath = FileStorageUtils.getSavePath(accountName)

val moved = moveLocalFiles(accountName, ocFile, defaultSavePath, targetPath)
if (!moved) return

val originalMediaPaths =
fileDao.moveFilesInDb(oldPath, targetPath, defaultSavePath, targetParent.fileId, accountName)

for (originalMediaPath in originalMediaPaths) {
deleteFileInMediaScan(originalMediaPath)
val newMediaPath = defaultSavePath + targetPath + originalMediaPath.substring(
(defaultSavePath + oldPath).length
)
FileDataStorageManager.triggerMediaScan(newMediaPath)
}
}

@Suppress("ReturnCount")
private fun moveLocalFiles(accountName: String, ocFile: OCFile, defaultSavePath: String, targetPath: String): Boolean {
val localFile = File(FileStorageUtils.getDefaultSavePathFor(accountName, ocFile))
if (!localFile.exists()) {
Log_OC.d(FileDataStorageManager.TAG, "moveLocalFile: no local file to move at " + localFile.absolutePath)
return false
}

val targetFile = File(defaultSavePath + targetPath)
val targetFolder = targetFile.getParentFile()
if (targetFolder != null && !targetFolder.exists() && !targetFolder.mkdirs()) {
Log_OC.e(
FileDataStorageManager.TAG,
"moveLocalFile: failed to create parent folder " + targetFolder.absolutePath
)
}

if (!localFile.renameTo(targetFile)) {
Log_OC.e(
FileDataStorageManager.TAG,
(
"moveLocalFile: failed to rename " + localFile.absolutePath +
" to " + targetFile.absolutePath
)
)
return false
}

return true
}

private fun FileDao.moveFilesInDb(
oldPath: String,
targetPath: String,
defaultSavePath: String,
targetParentId: Long,
accountName: String
): List<String> {
val entities = getFolderWithDescendants("$oldPath%", accountName)
val oldStoragePrefix = defaultSavePath + oldPath
val newStoragePrefix = defaultSavePath + targetPath

val originalMediaPaths = entities
.filter { MimeTypeUtil.isMedia(it.contentType) && it.storagePath?.startsWith(oldStoragePrefix) == true }
.mapNotNull { it.storagePath }

val updated = entities.map { entity ->
val currentPath = entity.path.orEmpty()
val newPath = targetPath + currentPath.substring(oldPath.length)
entity.copy(
path = newPath,
pathDecrypted = if (entity.isEncrypted == 0) newPath else entity.pathDecrypted,
storagePath = if (entity.storagePath?.startsWith(oldStoragePrefix) == true) {
newStoragePrefix + entity.storagePath.substring(oldStoragePrefix.length)
} else {
entity.storagePath
},
parent = if (currentPath == oldPath) targetParentId else entity.parent
)
}

updateAll(updated)
return originalMediaPaths
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.media.MediaScannerConnection;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.MediaStore;
Expand All @@ -48,8 +48,8 @@
import com.nextcloud.model.ShareeEntry;
import com.nextcloud.utils.date.DateFormatPattern;
import com.nextcloud.utils.extensions.DateExtensionsKt;
import com.nextcloud.utils.extensions.FileDataStorageManagerExtensionsKt;
import com.nextcloud.utils.extensions.FileExtensionsKt;
import com.nextcloud.utils.extensions.StringExtensionsKt;
import com.owncloud.android.MainApp;
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
import com.owncloud.android.lib.common.network.WebdavEntry;
Expand Down Expand Up @@ -94,7 +94,7 @@

@SuppressFBWarnings("CE")
public class FileDataStorageManager {
private static final String TAG = FileDataStorageManager.class.getSimpleName();
public static final String TAG = FileDataStorageManager.class.getSimpleName();

private static final String AND = " = ? AND ";
private static final String FAILED_TO_INSERT_MSG = "Fail to insert insert file to database ";
Expand Down Expand Up @@ -1108,113 +1108,9 @@ private boolean removeLocalFolder(File localFolder) {

/**
* Updates database and file system for a file or folder that was moved to a different location.
* <p>
* TODO explore better (faster) implementations TODO throw exceptions up !
*/
public void moveLocalFile(OCFile ocFile, String targetPath, String targetParentPath) {
if (ocFile.fileExists() && !OCFile.ROOT_PATH.equals(ocFile.getFileName())) {

OCFile targetParent = getFileByPath(targetParentPath);
if (targetParent == null) {
throw new IllegalStateException("Parent folder of the target path does not exist!!");
}

String oldPath = ocFile.getRemotePath();

/// 1. get all the descendants of the moved element in a single QUERY
List<FileEntity> fileEntities =
fileDao.getFolderWithDescendants(oldPath + "%", user.getAccountName());

/// 2. prepare a batch of update operations to change all the descendants
ArrayList<ContentProviderOperation> operations = new ArrayList<>(fileEntities.size());
String defaultSavePath = FileStorageUtils.getSavePath(user.getAccountName());
List<String> originalPathsToTriggerMediaScan = new ArrayList<>();
List<String> newPathsToTriggerMediaScan = new ArrayList<>();

int lengthOfOldPath = oldPath.length();
int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath;
for (FileEntity fileEntity : fileEntities) {
ContentValues contentValues = new ContentValues(); // keep construction in the loop
OCFile childFile = createFileInstance(fileEntity);
contentValues.put(
ProviderTableMeta.FILE_PATH,
targetPath + childFile.getRemotePath().substring(lengthOfOldPath)
);

if (!childFile.isEncrypted()) {
contentValues.put(
ProviderTableMeta.FILE_PATH_DECRYPTED,
targetPath + childFile.getRemotePath().substring(lengthOfOldPath)
);
}

if (childFile.getStoragePath() != null && childFile.getStoragePath().startsWith(defaultSavePath)) {
// update link to downloaded content - but local move is not done here!
String targetLocalPath = defaultSavePath + targetPath +
childFile.getStoragePath().substring(lengthOfOldStoragePath);

contentValues.put(ProviderTableMeta.FILE_STORAGE_PATH, targetLocalPath);

if (MimeTypeUtil.isMedia(childFile.getMimeType())) {
originalPathsToTriggerMediaScan.add(childFile.getStoragePath());
newPathsToTriggerMediaScan.add(targetLocalPath);
}

}

if (childFile.getRemotePath().equals(ocFile.getRemotePath())) {
contentValues.put(ProviderTableMeta.FILE_PARENT, targetParent.getFileId());
}

operations.add(
ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI)
.withValues(contentValues)
.withSelection(ProviderTableMeta._ID + " = ?", new String[]{String.valueOf(childFile.getFileId())})
.build());

}

/// 3. apply updates in batch
try {
if (getContentResolver() != null) {
getContentResolver().applyBatch(MainApp.getAuthority(), operations);
} else {
getContentProviderClient().applyBatch(operations);
}

} catch (Exception e) {
Log_OC.e(TAG, "Fail to update " + ocFile.getFileId() + " and descendants in database", e);
}

/// 4. move in local file system
String originalLocalPath = FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), ocFile);
String targetLocalPath = defaultSavePath + targetPath;
File localFile = new File(originalLocalPath);
boolean renamed = false;

if (localFile.exists()) {
File targetFile = new File(targetLocalPath);
File targetFolder = targetFile.getParentFile();
if (targetFolder != null && !targetFolder.exists() && !targetFolder.mkdirs()) {
Log_OC.e(TAG, "Unable to create parent folder " + targetFolder.getAbsolutePath());
}
renamed = localFile.renameTo(targetFile);
}

if (renamed) {
Iterator<String> pathIterator = originalPathsToTriggerMediaScan.iterator();
while (pathIterator.hasNext()) {
// Notify MediaScanner about removed file
deleteFileInMediaScan(pathIterator.next());
}

pathIterator = newPathsToTriggerMediaScan.iterator();
while (pathIterator.hasNext()) {
// Notify MediaScanner about new file/folder
triggerMediaScan(pathIterator.next());
}
}
}
FileDataStorageManagerExtensionsKt.moveFiles(this, ocFile, targetPath, targetParentPath);
}

public void copyLocalFile(OCFile ocFile, String targetPath) {
Expand Down
Loading
Loading