You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
642 lines
23 KiB
642 lines
23 KiB
#include "filetransferjob.h"
|
|
#include "totalsizejob.h"
|
|
#include "fileinfo_p.h"
|
|
|
|
namespace Fm {
|
|
|
|
FileTransferJob::FileTransferJob(FilePathList srcPaths, Mode mode):
|
|
FileOperationJob{},
|
|
srcPaths_{std::move(srcPaths)},
|
|
mode_{mode} {
|
|
}
|
|
|
|
FileTransferJob::FileTransferJob(FilePathList srcPaths, FilePathList destPaths, Mode mode):
|
|
FileTransferJob{std::move(srcPaths), mode} {
|
|
destPaths_ = std::move(destPaths);
|
|
}
|
|
|
|
FileTransferJob::FileTransferJob(FilePathList srcPaths, const FilePath& destDirPath, Mode mode):
|
|
FileTransferJob{std::move(srcPaths), mode} {
|
|
setDestDirPath(destDirPath);
|
|
}
|
|
|
|
void FileTransferJob::setSrcPaths(FilePathList srcPaths) {
|
|
srcPaths_ = std::move(srcPaths);
|
|
}
|
|
|
|
void FileTransferJob::setDestPaths(FilePathList destPaths) {
|
|
destPaths_ = std::move(destPaths);
|
|
}
|
|
|
|
void FileTransferJob::setDestDirPath(const FilePath& destDirPath) {
|
|
destPaths_.clear();
|
|
destPaths_.reserve(srcPaths_.size());
|
|
for(const auto& srcPath: srcPaths_) {
|
|
FilePath destPath;
|
|
if(mode_ == Mode::LINK && !srcPath.isNative()) {
|
|
// special handling for URLs
|
|
auto fullBasename = srcPath.baseName();
|
|
char* basename = fullBasename.get();
|
|
char* dname = nullptr;
|
|
// if we drop URI query onto native filesystem, omit query part
|
|
if(!srcPath.isNative()) {
|
|
dname = strchr(basename, '?');
|
|
}
|
|
// if basename consist only from query then use first part of it
|
|
if(dname == basename) {
|
|
basename++;
|
|
dname = strchr(basename, '&');
|
|
}
|
|
|
|
CStrPtr _basename;
|
|
if(dname) {
|
|
_basename = CStrPtr{g_strndup(basename, dname - basename)};
|
|
dname = strrchr(_basename.get(), G_DIR_SEPARATOR);
|
|
g_debug("cutting '%s' to '%s'", basename, dname ? &dname[1] : _basename.get());
|
|
if(dname) {
|
|
basename = &dname[1];
|
|
}
|
|
else {
|
|
basename = _basename.get();
|
|
}
|
|
}
|
|
destPath = destDirPath.child(basename);
|
|
}
|
|
else {
|
|
destPath = destDirPath.child(srcPath.baseName().get());
|
|
}
|
|
destPaths_.emplace_back(std::move(destPath));
|
|
}
|
|
}
|
|
|
|
void FileTransferJob::gfileCopyProgressCallback(goffset current_num_bytes, goffset total_num_bytes, FileTransferJob* _this) {
|
|
_this->setCurrentFileProgress(total_num_bytes, current_num_bytes);
|
|
}
|
|
|
|
bool FileTransferJob::moveFileSameFs(const FilePath& srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath) {
|
|
int flags = G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS;
|
|
GErrorPtr err;
|
|
bool retry;
|
|
do {
|
|
retry = false;
|
|
err.reset();
|
|
// do the file operation
|
|
if(!g_file_move(srcPath.gfile().get(), destPath.gfile().get(), GFileCopyFlags(flags), cancellable().get(),
|
|
nullptr, this, &err)) {
|
|
retry = handleError(err, srcPath, srcInfo, destPath, flags);
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
} while(retry && !isCancelled());
|
|
return false;
|
|
}
|
|
|
|
bool FileTransferJob::copyRegularFile(const FilePath& srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath) {
|
|
int flags = G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS;
|
|
GErrorPtr err;
|
|
bool retry;
|
|
do {
|
|
retry = false;
|
|
err.reset();
|
|
|
|
// reset progress of the current file (only for copy)
|
|
auto size = g_file_info_get_size(srcInfo.get());
|
|
setCurrentFileProgress(size, 0);
|
|
|
|
// do the file operation
|
|
if(!g_file_copy(srcPath.gfile().get(), destPath.gfile().get(), GFileCopyFlags(flags), cancellable().get(),
|
|
(GFileProgressCallback)&gfileCopyProgressCallback, this, &err)) {
|
|
retry = handleError(err, srcPath, srcInfo, destPath, flags);
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
} while(retry && !isCancelled());
|
|
return false;
|
|
}
|
|
|
|
bool FileTransferJob::copySpecialFile(const FilePath& srcPath, const GFileInfoPtr& srcInfo, FilePath &destPath) {
|
|
bool ret = false;
|
|
// only handle FIFO for local files
|
|
if(srcPath.isNative() && destPath.isNative()) {
|
|
auto src_path = srcPath.localPath();
|
|
struct stat src_st;
|
|
int r;
|
|
r = lstat(src_path.get(), &src_st);
|
|
if(r == 0) {
|
|
// Handle FIFO on native file systems.
|
|
if(S_ISFIFO(src_st.st_mode)) {
|
|
auto dest_path = destPath.localPath();
|
|
if(mkfifo(dest_path.get(), src_st.st_mode) == 0) {
|
|
ret = true;
|
|
}
|
|
}
|
|
// FIXME: how about block device, char device, and socket?
|
|
}
|
|
}
|
|
if(!ret) {
|
|
GErrorPtr err;
|
|
g_set_error(&err, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
("Cannot copy file '%s': not supported"),
|
|
g_file_info_get_display_name(srcInfo.get()));
|
|
emitError(err, ErrorSeverity::MODERATE);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool FileTransferJob::copyDirContent(const FilePath& srcPath, GFileInfoPtr srcInfo, FilePath& destPath, bool skip) {
|
|
bool ret = false;
|
|
// copy dir content
|
|
GErrorPtr err;
|
|
auto enu = GFileEnumeratorPtr{
|
|
g_file_enumerate_children(srcPath.gfile().get(),
|
|
defaultGFileInfoQueryAttribs,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable().get(), &err),
|
|
false};
|
|
if(enu) {
|
|
int n_children = 0;
|
|
int n_copied = 0;
|
|
ret = true;
|
|
while(!isCancelled()) {
|
|
err.reset();
|
|
GFileInfoPtr inf{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false};
|
|
if(inf) {
|
|
++n_children;
|
|
const char* name = g_file_info_get_name(inf.get());
|
|
FilePath childPath = srcPath.child(name);
|
|
bool child_ret = copyFile(childPath, inf, destPath, name, skip);
|
|
if(child_ret) {
|
|
++n_copied;
|
|
}
|
|
else {
|
|
ret = false;
|
|
}
|
|
}
|
|
else {
|
|
if(err) {
|
|
// fail to read directory content
|
|
// NOTE: since we cannot read the source dir, we cannot calculate the progress correctly, either.
|
|
emitError(err, ErrorSeverity::MODERATE);
|
|
err.reset();
|
|
/* ErrorAction::RETRY is not supported here */
|
|
ret = false;
|
|
}
|
|
else { /* EOF is reached */
|
|
/* all files are successfully copied. */
|
|
if(isCancelled()) {
|
|
ret = false;
|
|
}
|
|
else {
|
|
/* some files are not copied */
|
|
if(n_children != n_copied) {
|
|
/* if the copy actions are skipped deliberately, it's ok */
|
|
if(!skip) {
|
|
ret = false;
|
|
}
|
|
}
|
|
/* else job->skip_dir_content is true */
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
g_file_enumerator_close(enu.get(), nullptr, &err);
|
|
}
|
|
else {
|
|
if(err) {
|
|
emitError(err, ErrorSeverity::MODERATE);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool FileTransferJob::makeDir(const FilePath& srcPath, GFileInfoPtr srcInfo, FilePath& destPath) {
|
|
if(isCancelled()) {
|
|
return false;
|
|
}
|
|
|
|
bool mkdir_done = false;
|
|
do {
|
|
GErrorPtr err;
|
|
mkdir_done = g_file_make_directory_with_parents(destPath.gfile().get(), cancellable().get(), &err);
|
|
if(!mkdir_done) {
|
|
if(err->domain == G_IO_ERROR && (err->code == G_IO_ERROR_EXISTS ||
|
|
err->code == G_IO_ERROR_INVALID_FILENAME ||
|
|
err->code == G_IO_ERROR_FILENAME_TOO_LONG)) {
|
|
GFileInfoPtr destInfo = GFileInfoPtr {
|
|
g_file_query_info(destPath.gfile().get(),
|
|
defaultGFileInfoQueryAttribs,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable().get(), nullptr),
|
|
false
|
|
};
|
|
if(!destInfo) {
|
|
// FIXME: error handling
|
|
break;
|
|
}
|
|
|
|
FilePath newDestPath;
|
|
FileExistsAction opt = askRename(FileInfo{srcInfo, srcPath.parent()}, FileInfo{destInfo, destPath.parent()}, newDestPath);
|
|
switch(opt) {
|
|
case FileOperationJob::RENAME:
|
|
destPath = std::move(newDestPath);
|
|
break;
|
|
case FileOperationJob::SKIP:
|
|
/* when a dir is skipped, we need to know its total size to calculate correct progress */
|
|
mkdir_done = true; /* pretend that dir creation succeeded */
|
|
break;
|
|
case FileOperationJob::OVERWRITE:
|
|
mkdir_done = true; /* pretend that dir creation succeeded */
|
|
break;
|
|
case FileOperationJob::CANCEL:
|
|
cancel();
|
|
return false;
|
|
case FileOperationJob::SKIP_ERROR: ; /* FIXME */
|
|
}
|
|
}
|
|
else {
|
|
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
|
|
if(act != ErrorAction::RETRY) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} while(!mkdir_done && !isCancelled());
|
|
|
|
bool chmod_done = false;
|
|
if(mkdir_done && !isCancelled()) {
|
|
mode_t mode = g_file_info_get_attribute_uint32(srcInfo.get(), G_FILE_ATTRIBUTE_UNIX_MODE);
|
|
if(mode) {
|
|
mode |= (S_IRUSR | S_IWUSR); /* ensure we have rw permission to this file. */
|
|
do {
|
|
GErrorPtr err;
|
|
// chmod the newly created dir properly
|
|
// if(!fm_job_is_cancelled(fmjob) && !job->skip_dir_content)
|
|
chmod_done = g_file_set_attribute_uint32(destPath.gfile().get(),
|
|
G_FILE_ATTRIBUTE_UNIX_MODE,
|
|
mode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable().get(), &err);
|
|
if(!chmod_done) {
|
|
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
|
|
if(act != ErrorAction::RETRY) {
|
|
break;
|
|
}
|
|
/* FIXME: some filesystems may not support this. */
|
|
}
|
|
} while(!chmod_done && !isCancelled());
|
|
}
|
|
}
|
|
return mkdir_done && chmod_done;
|
|
}
|
|
|
|
bool FileTransferJob::handleError(GErrorPtr &err, const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath, int& flags) {
|
|
bool retry = false;
|
|
/* handle existing files or file name conflict */
|
|
if(err.domain() == G_IO_ERROR && (err.code() == G_IO_ERROR_EXISTS ||
|
|
err.code() == G_IO_ERROR_INVALID_FILENAME ||
|
|
err.code() == G_IO_ERROR_FILENAME_TOO_LONG)) {
|
|
flags &= ~G_FILE_COPY_OVERWRITE;
|
|
|
|
// get info of the existing file
|
|
GFileInfoPtr destInfo = GFileInfoPtr {
|
|
g_file_query_info(destPath.gfile().get(),
|
|
defaultGFileInfoQueryAttribs,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable().get(), nullptr),
|
|
false
|
|
};
|
|
|
|
// ask the user to rename or overwrite the existing file
|
|
if(!isCancelled() && destInfo) {
|
|
FilePath newDestPath;
|
|
FileExistsAction opt = askRename(FileInfo{srcInfo, srcPath.parent()},
|
|
FileInfo{destInfo, destPath.parent()},
|
|
newDestPath);
|
|
switch(opt) {
|
|
case FileOperationJob::RENAME:
|
|
// try a new file name
|
|
if(newDestPath.isValid()) {
|
|
destPath = std::move(newDestPath);
|
|
// FIXME: handle the error when newDestPath is invalid.
|
|
}
|
|
retry = true;
|
|
break;
|
|
case FileOperationJob::OVERWRITE:
|
|
// overwrite existing file
|
|
flags |= G_FILE_COPY_OVERWRITE;
|
|
retry = true;
|
|
err.reset();
|
|
break;
|
|
case FileOperationJob::CANCEL:
|
|
// cancel the whole job.
|
|
cancel();
|
|
break;
|
|
case FileOperationJob::SKIP:
|
|
// skip current file and don't copy it
|
|
case FileOperationJob::SKIP_ERROR: ; /* FIXME */
|
|
retry = false;
|
|
break;
|
|
}
|
|
err.reset();
|
|
}
|
|
}
|
|
|
|
// show error message
|
|
if(!isCancelled() && err) {
|
|
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
|
|
err.reset();
|
|
if(act == ErrorAction::RETRY) {
|
|
// the user wants retry the operation again
|
|
retry = true;
|
|
}
|
|
const bool is_no_space = (err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_NO_SPACE);
|
|
/* FIXME: ask to leave partial content? */
|
|
if(is_no_space) {
|
|
// run out of disk space. delete the partial content we copied.
|
|
g_file_delete(destPath.gfile().get(), cancellable().get(), nullptr);
|
|
}
|
|
}
|
|
return retry;
|
|
}
|
|
|
|
bool FileTransferJob::processPath(const FilePath& srcPath, const FilePath& destDirPath, const char* destFileName) {
|
|
GErrorPtr err;
|
|
GFileInfoPtr srcInfo = GFileInfoPtr {
|
|
g_file_query_info(srcPath.gfile().get(),
|
|
defaultGFileInfoQueryAttribs,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable().get(), &err),
|
|
false
|
|
};
|
|
if(!srcInfo || isCancelled()) {
|
|
// FIXME: report error
|
|
return false;
|
|
}
|
|
|
|
bool ret;
|
|
switch(mode_) {
|
|
case Mode::MOVE:
|
|
ret = moveFile(srcPath, srcInfo, destDirPath, destFileName);
|
|
break;
|
|
case Mode::COPY: {
|
|
bool deleteSrc = false;
|
|
ret = copyFile(srcPath, srcInfo, destDirPath, destFileName, deleteSrc);
|
|
break;
|
|
}
|
|
case Mode::LINK:
|
|
ret = linkFile(srcPath, srcInfo, destDirPath, destFileName);
|
|
break;
|
|
default:
|
|
ret = false;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool FileTransferJob::moveFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName) {
|
|
setCurrentFile(srcPath);
|
|
|
|
GErrorPtr err;
|
|
GFileInfoPtr destDirInfo = GFileInfoPtr {
|
|
g_file_query_info(destDirPath.gfile().get(),
|
|
"id::filesystem",
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable().get(), &err),
|
|
false
|
|
};
|
|
|
|
if(!destDirInfo || isCancelled()) {
|
|
// FIXME: report errors
|
|
return false;
|
|
}
|
|
|
|
// If src and dest are on the same filesystem, do move.
|
|
// Exception: if src FS is trash:///, we always do move
|
|
// Otherwise, do copy & delete src files.
|
|
auto src_fs = g_file_info_get_attribute_string(srcInfo.get(), "id::filesystem");
|
|
auto dest_fs = g_file_info_get_attribute_string(destDirInfo.get(), "id::filesystem");
|
|
bool ret;
|
|
if(src_fs && dest_fs && (strcmp(src_fs, dest_fs) == 0 || g_str_has_prefix(src_fs, "trash"))) {
|
|
// src and dest are on the same filesystem
|
|
auto destPath = destDirPath.child(destFileName);
|
|
ret = moveFileSameFs(srcPath, srcInfo, destPath);
|
|
|
|
// increase current progress
|
|
// FIXME: it's not appropriate to calculate the progress of move operations using file size
|
|
// since the time required to move a file is not related to it's file size.
|
|
auto size = g_file_info_get_size(srcInfo.get());
|
|
addFinishedAmount(size, 1);
|
|
}
|
|
else {
|
|
// cross device/filesystem move: copy & delete
|
|
ret = copyFile(srcPath, srcInfo, destDirPath, destFileName);
|
|
// NOTE: do not need to increase progress here since it's done by copyPath().
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool FileTransferJob::copyFile(const FilePath& srcPath, const GFileInfoPtr& srcInfo, const FilePath& destDirPath, const char* destFileName, bool skip) {
|
|
setCurrentFile(srcPath);
|
|
|
|
auto size = g_file_info_get_size(srcInfo.get());
|
|
bool success = false;
|
|
setCurrentFileProgress(size, 0);
|
|
|
|
auto destPath = destDirPath.child(destFileName);
|
|
auto file_type = g_file_info_get_file_type(srcInfo.get());
|
|
if(!skip) {
|
|
switch(file_type) {
|
|
case G_FILE_TYPE_DIRECTORY:
|
|
success = makeDir(srcPath, srcInfo, destPath);
|
|
break;
|
|
case G_FILE_TYPE_SPECIAL:
|
|
success = copySpecialFile(srcPath, srcInfo, destPath);
|
|
break;
|
|
default:
|
|
success = copyRegularFile(srcPath, srcInfo, destPath);
|
|
break;
|
|
}
|
|
}
|
|
else { // skip the file
|
|
success = true;
|
|
}
|
|
|
|
if(success) {
|
|
// finish copying the file
|
|
addFinishedAmount(size, 1);
|
|
setCurrentFileProgress(0, 0);
|
|
|
|
// recursively copy dir content
|
|
if(file_type == G_FILE_TYPE_DIRECTORY) {
|
|
success = copyDirContent(srcPath, srcInfo, destPath, skip);
|
|
}
|
|
|
|
if(!skip && success && mode_ == Mode::MOVE) {
|
|
// delete the source file for cross-filesystem move
|
|
GErrorPtr err;
|
|
if(g_file_delete(srcPath.gfile().get(), cancellable().get(), &err)) {
|
|
// FIXME: add some file size to represent the amount of work need to delete a file
|
|
addFinishedAmount(1, 1);
|
|
}
|
|
else {
|
|
success = false;
|
|
}
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool FileTransferJob::linkFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName) {
|
|
setCurrentFile(srcPath);
|
|
|
|
bool ret = false;
|
|
// cannot create links on non-native filesystems
|
|
if(!destDirPath.isNative()) {
|
|
auto msg = tr("Cannot create a link on non-native filesystem");
|
|
GErrorPtr err{g_error_new_literal(G_IO_ERROR, G_IO_ERROR_FAILED, msg.toUtf8().constData())};
|
|
emitError(err, ErrorSeverity::CRITICAL);
|
|
return false;
|
|
}
|
|
|
|
if(srcPath.isNative()) {
|
|
// create symlinks for native files
|
|
auto destPath = destDirPath.child(destFileName);
|
|
ret = createSymlink(srcPath, srcInfo, destPath);
|
|
}
|
|
else {
|
|
// ensure that the dest file has *.desktop filename extension.
|
|
CStrPtr desktopEntryFileName{g_strconcat(destFileName, ".desktop", nullptr)};
|
|
auto destPath = destDirPath.child(desktopEntryFileName.get());
|
|
ret = createShortcut(srcPath, srcInfo, destPath);
|
|
}
|
|
|
|
// update progress
|
|
// FIXME: increase the progress by 1 byte is not appropriate
|
|
addFinishedAmount(1, 1);
|
|
return ret;
|
|
}
|
|
|
|
bool FileTransferJob::createSymlink(const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath) {
|
|
bool ret = false;
|
|
auto src = srcPath.localPath();
|
|
int flags = 0;
|
|
GErrorPtr err;
|
|
bool retry;
|
|
do {
|
|
retry = false;
|
|
if(flags & G_FILE_COPY_OVERWRITE) { // overwrite existing file
|
|
// creating symlink cannot overwrite existing files directly, so we delete the existing file first.
|
|
g_file_delete(destPath.gfile().get(), cancellable().get(), nullptr);
|
|
}
|
|
if(!g_file_make_symbolic_link(destPath.gfile().get(), src.get(), cancellable().get(), &err)) {
|
|
retry = handleError(err, srcPath, srcInfo, destPath, flags);
|
|
}
|
|
else {
|
|
ret = true;
|
|
break;
|
|
}
|
|
} while(!isCancelled() && retry);
|
|
return ret;
|
|
}
|
|
|
|
bool FileTransferJob::createShortcut(const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath) {
|
|
bool ret = false;
|
|
const char* iconName = nullptr;
|
|
GIcon* icon = g_file_info_get_icon(srcInfo.get());
|
|
if(icon && G_IS_THEMED_ICON(icon)) {
|
|
auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(icon));
|
|
if(iconNames && iconNames[0]) {
|
|
iconName = iconNames[0];
|
|
}
|
|
}
|
|
|
|
CStrPtr srcPathUri;
|
|
auto uri = g_file_info_get_attribute_string(srcInfo.get(), G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
|
|
if(!uri) {
|
|
srcPathUri = srcPath.uri();
|
|
uri = srcPathUri.get();
|
|
}
|
|
|
|
CStrPtr srcPathDispName;
|
|
auto name = g_file_info_get_display_name(srcInfo.get());
|
|
if(!name) {
|
|
srcPathDispName = srcPath.displayName();
|
|
name = srcPathDispName.get();
|
|
}
|
|
|
|
GKeyFile* kf = g_key_file_new();
|
|
if(kf) {
|
|
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TYPE, "Link");
|
|
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, name);
|
|
if(iconName) {
|
|
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, iconName);
|
|
}
|
|
if(uri) {
|
|
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_URL, uri);
|
|
}
|
|
gsize contentLen;
|
|
CStrPtr content{g_key_file_to_data(kf, &contentLen, nullptr)};
|
|
g_key_file_free(kf);
|
|
|
|
int flags = 0;
|
|
if(content) {
|
|
bool retry;
|
|
GErrorPtr err;
|
|
do {
|
|
retry = false;
|
|
if(flags & G_FILE_COPY_OVERWRITE) { // overwrite existing file
|
|
g_file_delete(destPath.gfile().get(), cancellable().get(), nullptr);
|
|
}
|
|
|
|
if(!g_file_replace_contents(destPath.gfile().get(), content.get(), contentLen, nullptr, false, G_FILE_CREATE_NONE, nullptr, cancellable().get(), &err)) {
|
|
retry = handleError(err, srcPath, srcInfo, destPath, flags);
|
|
err.reset();
|
|
}
|
|
else {
|
|
ret = true;
|
|
}
|
|
} while(!isCancelled() && retry);
|
|
ret = true;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
void FileTransferJob::exec() {
|
|
// calculate the total size of files to copy
|
|
auto totalSizeFlags = (mode_ == Mode::COPY ? TotalSizeJob::DEFAULT : TotalSizeJob::PREPARE_MOVE);
|
|
TotalSizeJob totalSizeJob{srcPaths_, totalSizeFlags};
|
|
connect(&totalSizeJob, &TotalSizeJob::error, this, &FileTransferJob::error);
|
|
connect(this, &FileTransferJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel);
|
|
totalSizeJob.run();
|
|
if(isCancelled()) {
|
|
return;
|
|
}
|
|
|
|
// ready to start
|
|
setTotalAmount(totalSizeJob.totalSize(), totalSizeJob.fileCount());
|
|
Q_EMIT preparedToRun();
|
|
|
|
if(srcPaths_.size() != destPaths_.size()) {
|
|
qWarning("error: srcPaths.size() != destPaths.size() when copying files");
|
|
return;
|
|
}
|
|
|
|
// copy the files
|
|
for(size_t i = 0; i < srcPaths_.size(); ++i) {
|
|
if(isCancelled()) {
|
|
break;
|
|
}
|
|
const auto& srcPath = srcPaths_[i];
|
|
const auto& destPath = destPaths_[i];
|
|
auto destDirPath = destPath.parent();
|
|
processPath(srcPath, destDirPath, destPath.baseName().get());
|
|
}
|
|
}
|
|
|
|
|
|
} // namespace Fm
|