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.
libfm-qt-packaging/src/core/copyjob.cpp

454 lines
16 KiB

#include "copyjob.h"
#include "totalsizejob.h"
#include "fileinfo_p.h"
namespace Fm {
CopyJob::CopyJob(const FilePathList& paths, const FilePath& destDirPath, Mode mode):
FileOperationJob{},
srcPaths_{paths},
destDirPath_{destDirPath},
mode_{mode},
skip_dir_content{false} {
}
CopyJob::CopyJob(const FilePathList &&paths, const FilePath &&destDirPath, Mode mode):
FileOperationJob{},
srcPaths_{paths},
destDirPath_{destDirPath},
mode_{mode},
skip_dir_content{false} {
}
void CopyJob::gfileProgressCallback(goffset current_num_bytes, goffset total_num_bytes, CopyJob* _this) {
_this->setCurrentFileProgress(total_num_bytes, current_num_bytes);
}
bool CopyJob::copyRegularFile(const FilePath& srcPath, GFileInfoPtr /*srcFile*/, const FilePath& destPath) {
int flags = G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS;
GErrorPtr err;
_retry_copy:
if(!g_file_copy(srcPath.gfile().get(), destPath.gfile().get(), GFileCopyFlags(flags), cancellable().get(),
GFileProgressCallback(gfileProgressCallback), this, &err)) {
flags &= ~G_FILE_COPY_OVERWRITE;
/* 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)) {
#if 0
GFile* dest_cp = new_dest;
bool dest_exists = (err->code == G_IO_ERROR_EXISTS);
FmFileOpOption opt = 0;
g_error_free(err);
err = nullptr;
new_dest = nullptr;
opt = _fm_file_ops_job_ask_new_name(job, src, dest, &new_dest, dest_exists);
if(!new_dest) { /* restoring status quo */
new_dest = dest_cp;
}
else if(dest_cp) { /* we got new new_dest, forget old one */
g_object_unref(dest_cp);
}
switch(opt) {
case FM_FILE_OP_RENAME:
dest = new_dest;
goto _retry_copy;
break;
case FM_FILE_OP_OVERWRITE:
flags |= G_FILE_COPY_OVERWRITE;
goto _retry_copy;
break;
case FM_FILE_OP_CANCEL:
fm_job_cancel(fmjob);
break;
case FM_FILE_OP_SKIP:
ret = true;
delete_src = false; /* don't delete source file. */
break;
case FM_FILE_OP_SKIP_ERROR: ; /* FIXME */
}
#endif
}
else {
ErrorAction act = emitError( err, ErrorSeverity::MODERATE);
err.reset();
if(act == ErrorAction::RETRY) {
// FIXME: job->current_file_finished = 0;
goto _retry_copy;
}
# if 0
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) {
g_file_delete(dest, fm_job_get_cancellable(fmjob), nullptr);
}
ret = false;
delete_src = false;
#endif
}
err.reset();
}
else {
return true;
}
return false;
}
bool CopyJob::copySpecialFile(const FilePath& srcPath, GFileInfoPtr srcFile, const FilePath& destPath) {
bool ret = false;
GError* err = nullptr;
/* 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) {
g_set_error(&err, G_IO_ERROR, G_IO_ERROR_FAILED,
("Cannot copy file '%s': not supported"),
g_file_info_get_display_name(srcFile.get()));
// emitError( err, ErrorSeverity::MODERATE);
g_clear_error(&err);
}
return ret;
}
bool CopyJob::copyDir(const FilePath& srcPath, GFileInfoPtr srcFile, const FilePath& destPath) {
bool ret = false;
if(makeDir(srcPath, srcFile, destPath)) {
GError* err = nullptr;
auto enu = GFileEnumeratorPtr{
g_file_enumerate_children(srcPath.gfile().get(),
gfile_info_query_attribs,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err),
false};
if(enu) {
int n_children = 0;
int n_copied = 0;
ret = true;
while(!isCancelled()) {
auto inf = GFileInfoPtr{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false};
if(inf) {
++n_children;
/* don't overwrite dir content, only calculate progress. */
if(Q_UNLIKELY(skip_dir_content)) {
/* FIXME: this is incorrect as we don't do the calculation recursively. */
addFinishedAmount(g_file_info_get_size(inf.get()), 1);
}
else {
const char* name = g_file_info_get_name(inf.get());
FilePath childPath = srcPath.child(name);
bool child_ret = copyPath(childPath, inf, destPath, name);
if(child_ret) {
++n_copied;
}
else {
ret = false;
}
}
}
else {
if(err) {
// FIXME: emitError( err, ErrorSeverity::MODERATE);
g_error_free(err);
err = nullptr;
/* 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_dir_content) {
ret = false;
}
}
/* else job->skip_dir_content is true */
}
break;
}
}
}
g_file_enumerator_close(enu.get(), nullptr, &err);
}
}
return false;
}
bool CopyJob::makeDir(const FilePath& srcPath, GFileInfoPtr srcFile, const FilePath& dirPath) {
GError* err = nullptr;
if(isCancelled())
return false;
FilePath destPath = dirPath;
bool mkdir_done = false;
do {
mkdir_done = g_file_make_directory(destPath.gfile().get(), cancellable().get(), &err);
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 destFile;
// FIXME: query its info
FilePath newDestPath;
FileExistsAction opt = askRename(FileInfo{srcFile, srcPath.parent()}, FileInfo{destFile, dirPath.parent()}, newDestPath);
g_error_free(err);
err = nullptr;
switch(opt) {
case FileOperationJob::RENAME:
destPath = newDestPath;
break;
case FileOperationJob::SKIP:
/* when a dir is skipped, we need to know its total size to calculate correct progress */
// job->finished += size;
// fm_file_ops_job_emit_percent(job);
// job->skip_dir_content = skip_dir_content = true;
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();
break;
case FileOperationJob::SKIP_ERROR: ; /* FIXME */
}
}
else {
#if 0
ErrorAction act = emitError( err, ErrorSeverity::MODERATE);
g_error_free(err);
err = nullptr;
if(act == ErrorAction::RETRY) {
goto _retry_mkdir;
}
#endif
break;
}
// job->finished += size;
} while(!mkdir_done && !isCancelled());
if(mkdir_done && !isCancelled()) {
bool chmod_done = false;
mode_t mode = g_file_info_get_attribute_uint32(srcFile.get(), G_FILE_ATTRIBUTE_UNIX_MODE);
if(mode) {
mode |= (S_IRUSR | S_IWUSR); /* ensure we have rw permission to this file. */
do {
/* 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);
g_error_free(err);
err = nullptr;
if(act == ErrorAction::RETRY) {
goto _retry_chmod_for_dir;
}
*/
/* FIXME: some filesystems may not support this. */
}
} while(!chmod_done && !isCancelled());
// finished += size;
// fm_file_ops_job_emit_percent(job);
}
}
return false;
}
bool CopyJob::copyPath(const FilePath& srcPath, const FilePath& destDirPath, const char* destFileName) {
GErrorPtr err;
GFileInfoPtr srcInfo = GFileInfoPtr {
g_file_query_info(srcPath.gfile().get(),
gfile_info_query_attribs,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err),
false
};
if(!srcInfo || isCancelled()) {
return false;
}
return copyPath(srcPath, srcInfo, destDirPath, destFileName);
}
bool CopyJob::copyPath(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()) {
return false;
}
auto size = g_file_info_get_size(srcInfo.get());
setCurrentFileProgress(size, 0);
auto destPath = destDirPath.child(destFileName);
bool success = false;
switch(g_file_info_get_file_type(srcInfo.get())) {
case G_FILE_TYPE_DIRECTORY:
success = copyDir(srcPath, srcInfo, destPath);
break;
case G_FILE_TYPE_SPECIAL:
success = copySpecialFile(srcPath, srcInfo, destPath);
break;
default:
success = copyRegularFile(srcPath, srcInfo, destPath);
break;
}
if(success) {
addFinishedAmount(size, 1);
#if 0
if(ret && dest_folder) {
fm_dest = fm_path_new_for_gfile(dest);
if(!_fm_folder_event_file_added(dest_folder, fm_dest)) {
fm_path_unref(fm_dest);
}
}
#endif
}
return false;
}
#if 0
bool _fm_file_ops_job_copy_run(FmFileOpsJob* job) {
bool ret = true;
GFile* dest_dir;
GList* l;
FmJob* fmjob = FM_JOB(job);
/* prepare the job, count total work needed with FmDeepCountJob */
FmDeepCountJob* dc = fm_deep_count_job_new(job->srcs, FM_DC_JOB_DEFAULT);
FmFolder* df;
/* let the deep count job share the same cancellable object. */
fm_job_set_cancellable(FM_JOB(dc), fm_job_get_cancellable(fmjob));
fm_job_run_sync(FM_JOB(dc));
job->total = dc->total_size;
if(fm_job_is_cancelled(fmjob)) {
g_object_unref(dc);
return false;
}
g_object_unref(dc);
g_debug("total size to copy: %llu", (long long unsigned int)job->total);
dest_dir = fm_path_to_gfile(job->dest);
/* suspend updates for destination */
df = fm_folder_find_by_path(job->dest);
if(df) {
fm_folder_block_updates(df);
}
fm_file_ops_job_emit_prepared(job);
for(l = fm_path_list_peek_head_link(job->srcs); !fm_job_is_cancelled(fmjob) && l; l = l->next) {
FmPath* path = FM_PATH(l->data);
GFile* src = fm_path_to_gfile(path);
GFile* dest;
char* tmp_basename;
if(g_file_is_native(src) && g_file_is_native(dest_dir))
/* both are native */
{
tmp_basename = nullptr;
}
else if(g_file_is_native(src)) /* copy from native to virtual */
tmp_basename = g_filename_to_utf8(fm_path_get_basename(path),
-1, nullptr, nullptr, nullptr);
/* gvfs escapes it itself */
else { /* copy from virtual to native/virtual */
/* if we drop URI query onto native filesystem, omit query part */
const char* basename = fm_path_get_basename(path);
char* sub_name;
sub_name = strchr(basename, '?');
if(sub_name) {
sub_name = g_strndup(basename, sub_name - basename);
basename = strrchr(sub_name, G_DIR_SEPARATOR);
if(basename) {
basename++;
}
else {
basename = sub_name;
}
}
tmp_basename = fm_uri_subpath_to_native_subpath(basename, nullptr);
g_free(sub_name);
}
dest = g_file_get_child(dest_dir,
tmp_basename ? tmp_basename : fm_path_get_basename(path));
g_free(tmp_basename);
if(!_fm_file_ops_job_copy_file(job, src, nullptr, dest, nullptr, df)) {
ret = false;
}
g_object_unref(src);
g_object_unref(dest);
}
/* g_debug("finished: %llu, total: %llu", job->finished, job->total); */
fm_file_ops_job_emit_percent(job);
/* restore updates for destination */
if(df) {
fm_folder_unblock_updates(df);
g_object_unref(df);
}
g_object_unref(dest_dir);
return ret;
}
#endif
void CopyJob::exec() {
TotalSizeJob totalSizeJob{srcPaths_};
connect(&totalSizeJob, &TotalSizeJob::error, this, &CopyJob::error);
connect(this, &CopyJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel);
totalSizeJob.run();
if(isCancelled()) {
return;
}
setTotalAmount(totalSizeJob.totalSize(), totalSizeJob.fileCount());
Q_EMIT preparedToRun();
for(auto& srcPath : srcPaths_) {
if(isCancelled()) {
break;
}
copyPath(srcPath, destDirPath_, srcPath.baseName().get());
}
}
} // namespace Fm