454 lines
16 KiB
C++
454 lines
16 KiB
C++
#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
|